diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e94f8db..4613f1f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,7 +417,7 @@ if(BUILD_SLIMGUI) file(GLOB_RECURSE QTSLIM_SOURCES ${PROJECT_SOURCE_DIR}/QtSLiM/*.cpp ${PROJECT_SOURCE_DIR}/QtSLiM/*.qrc ${PROJECT_SOURCE_DIR}/eidos/*.cpp) add_executable(${TARGET_NAME_SLIMGUI} "${QTSLIM_SOURCES}" "${SLIM_SOURCES}") set_target_properties( ${TARGET_NAME_SLIMGUI} PROPERTIES LINKER_LANGUAGE CXX) - target_compile_definitions( ${TARGET_NAME_SLIMGUI} PRIVATE EIDOSGUI=1 SLIMGUI=1) + target_compile_definitions( ${TARGET_NAME_SLIMGUI} PRIVATE EIDOS_GUI=1 SLIMGUI=1) target_include_directories(${TARGET_NAME_SLIMGUI} PUBLIC ${GSL_INCLUDES} "${PROJECT_SOURCE_DIR}/QtSLiM" "${PROJECT_SOURCE_DIR}/eidos" "${PROJECT_SOURCE_DIR}/core" "${PROJECT_SOURCE_DIR}/treerec" "${PROJECT_SOURCE_DIR}/treerec/tskit/kastore") # Qt dependencies, which depend on the Qt version used. For Qt6, we also need C++17; the last -std flag supplied ought to take priority. 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 c93cd760..c8a1303c 100644 --- a/EidosScribe/EidosHelpFunctions.rtf +++ b/EidosScribe/EidosHelpFunctions.rtf @@ -3650,8 +3650,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..3eb3cecb 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->IsPureNeutralDES()) 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/help/EidosHelpFunctions.html b/QtSLiM/help/EidosHelpFunctions.html index 04b56d2d..eb4d3880 100644 --- a/QtSLiM/help/EidosHelpFunctions.html +++ b/QtSLiM/help/EidosHelpFunctions.html @@ -294,7 +294,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.

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

@@ -468,6 +466,10 @@

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

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

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

+

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

+

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

+

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

+

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

+ (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 +497,12 @@

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

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

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

+

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

+

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

+

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

+

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

+

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

+

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

+ (void)setSpatialPosition(float position)

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

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

@@ -666,6 +674,15 @@

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.

+

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

@@ -682,22 +699,30 @@

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

– (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 +734,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 +751,27 @@

tag <–> (integer$)

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

5.11.2  MutationType methods

-

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

-

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

-

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

-

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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 +855,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 +905,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 +990,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 +1038,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 +1054,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).

@@ -1083,6 +1108,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 +1282,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 +1313,8 @@

– (void)setCloningRate(numeric rate)

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

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

-

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

+

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

+

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

– (void)setSelfingRate(numeric$ rate)

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

– (void)setSexRatio(float$ sexRatio)

@@ -1312,27 +1342,58 @@

5.18.1  Substitution properties

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

+

dominance => (float)

+

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

+

effect => (float)

+

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

+

hemizygousDominance => (float)

+

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

id => (integer$)

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

fixationTick => (integer$)

The tick in which this mutation fixed.

mutationType => (object<MutationType>$)

The MutationType from which this mutation was drawn.

-

nucleotide <–> (string$)

+

nucleotide => (string$)

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

-

nucleotideValue <–> (integer$)

+

nucleotideValue => (integer$)

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

originTick => (integer$)

The tick in which this mutation arose.

position => (integer$)

The position in the chromosome of this mutation.

-

selectionCoeff => (float$)

-

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

subpopID <–> (integer$)

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

tag <–> (integer$)

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

5.18.2  Substitution methods

+

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

+

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

+

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

+

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

+

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

+

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

+

5.19  Class Trait

+

5.19.1  Trait properties

+

baselineOffset <–> (float$)

+

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

+

directFitnessEffect <–> (logical$)

+

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

+

index => (integer$)

+

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

+

individualOffsetMean <–> (float$)

+

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

+

individualOffsetSD <–> (float$)

+

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

+

name => (string$)

+

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

+

species => (object<Species>$)

+

The species to which the target object belongs.

+

tag <–> (integer$)

+

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

+

type => (string$)

+

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

+

5.19.2  Trait methods


diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index c2d4f6c5..25122212 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,12 +99,14 @@

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.

+

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

+

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

+

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

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

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

@@ -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 or Species classes.  These requirements are necessary because, after the new trait is created, new properties added to the Individual and Species classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named heightT, getting and setting an individual’s trait value would be possible through the property individual.heightT.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.heightT would provide the Trait object named heightT.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("heightT", heightT) would allow the Trait object to be referenced simply as heightT.  For clarity, it is suggested that trait names end in a "T" to differentiate them from other variables and properties, but this is not required.

+

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

+

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

+

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

+

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

+

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 5c057561..3a6d3ea4 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 */; }; @@ -640,6 +645,11 @@ 98ACDC9F253522B80038703F /* eidos_class_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98ACDC9C253522B80038703F /* eidos_class_Object.cpp */; }; 98ACDCA0253522B80038703F /* eidos_class_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98ACDC9C253522B80038703F /* eidos_class_Object.cpp */; }; 98ACDCA1253522B80038703F /* eidos_class_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98ACDC9C253522B80038703F /* eidos_class_Object.cpp */; }; + 98B6741E2E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; + 98B6741F2E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; + 98B674202E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; + 98B674212E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; + 98B674222E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; 98C0943E1B7663DF00766A9A /* female_symbol.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98C0943C1B7663DF00766A9A /* female_symbol.pdf */; }; 98C0943F1B7663DF00766A9A /* male_symbol.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98C0943D1B7663DF00766A9A /* male_symbol.pdf */; }; 98C821241C7A980000548839 /* inline.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C820E51C7A980000548839 /* inline.c */; }; @@ -1742,6 +1752,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 9801EEB52E9F28840017C779 /* mutation_block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mutation_block.h; sourceTree = ""; }; + 9801EEB62E9F28980017C779 /* mutation_block.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mutation_block.cpp; sourceTree = ""; }; 98024741215D85880025D29C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/FindRecipePanel.xib; sourceTree = ""; }; 980566E125A7C5B9008D3C7F /* fdist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fdist.c; sourceTree = ""; }; 98076602244934A800F6CBB4 /* zutil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zutil.h; sourceTree = ""; }; @@ -1959,6 +1971,8 @@ 986926EA1AA6B7480000E138 /* GraphView_PopulationVisualization.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GraphView_PopulationVisualization.mm; sourceTree = ""; }; 986D73E61B07E89E007FBB70 /* eidos_call_signature.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = eidos_call_signature.cpp; sourceTree = ""; }; 986D73E71B07E89E007FBB70 /* eidos_call_signature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eidos_call_signature.h; sourceTree = ""; }; + 987096582EB6937C008AFD5D /* pcg_extras.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pcg_extras.hpp; sourceTree = ""; }; + 987096592EB6937C008AFD5D /* pcg_random.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pcg_random.hpp; sourceTree = ""; }; 98729ACD2A87A93500E81662 /* eidos_sorting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = eidos_sorting.h; sourceTree = ""; }; 98729ACE2A87AAB700E81662 /* eidos_sorting.inc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.pascal; path = eidos_sorting.inc; sourceTree = ""; }; 98729AD72A87DFBE00E81662 /* eidos_sorting.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = eidos_sorting.cpp; sourceTree = ""; }; @@ -2039,6 +2053,8 @@ 98ACDC9C253522B80038703F /* eidos_class_Object.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = eidos_class_Object.cpp; sourceTree = ""; }; 98ACDCA2253522D40038703F /* eidos_class_Object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = eidos_class_Object.h; sourceTree = ""; }; 98AF84641CB307300030D1EB /* VERSIONS */ = {isa = PBXFileReference; lastKnownFileType = text; path = VERSIONS; sourceTree = ""; }; + 98B6741D2E981AD400930737 /* trait.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = trait.cpp; sourceTree = ""; }; + 98B674232E981AFD00930737 /* trait.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = trait.h; sourceTree = ""; }; 98B8AF8625A2BE7200C95D66 /* json.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = ""; }; 98B8AF8725A2BE7200C95D66 /* json_fwd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json_fwd.hpp; sourceTree = ""; }; 98C0943C1B7663DF00766A9A /* female_symbol.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = female_symbol.pdf; sourceTree = ""; }; @@ -2189,8 +2205,6 @@ 98D7D6642AB24CBC002AFE34 /* chisq.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = chisq.c; sourceTree = ""; }; 98D7EBEE28CE557C00DEAAC4 /* eidos_multi */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = eidos_multi; sourceTree = BUILT_PRODUCTS_DIR; }; 98D7ED2D28CE58FC00DEAAC4 /* slim_multi */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = slim_multi; sourceTree = BUILT_PRODUCTS_DIR; }; - 98D957882EB53494008314C1 /* pcg_extras.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pcg_extras.hpp; sourceTree = ""; }; - 98D957892EB53494008314C1 /* pcg_random.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pcg_random.hpp; sourceTree = ""; }; 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = interaction_type.cpp; sourceTree = ""; }; 98DB3D6E1E6122AE00E2C200 /* interaction_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = interaction_type.h; sourceTree = ""; }; 98DC9838289986B300160DD8 /* GitSHA1.cpp.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GitSHA1.cpp.in; sourceTree = ""; }; @@ -2391,8 +2405,8 @@ 98235687252FDD120096A745 /* lodepng.h */, 98235681252FDCF50096A745 /* lodepng.cpp */, 98186DB8254A8B1600F9118C /* robin_hood.h */, - 98D957882EB53494008314C1 /* pcg_extras.hpp */, - 98D957892EB53494008314C1 /* pcg_random.hpp */, + 987096582EB6937C008AFD5D /* pcg_extras.hpp */, + 987096592EB6937C008AFD5D /* pcg_random.hpp */, ); name = dependencies; sourceTree = ""; @@ -2518,6 +2532,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 */, @@ -2530,6 +2546,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 */, @@ -3871,6 +3889,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 */, @@ -3922,6 +3941,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 */, @@ -4047,6 +4067,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 */, @@ -4098,6 +4119,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 */, @@ -4446,6 +4468,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 */, @@ -4476,6 +4499,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 */, @@ -4797,6 +4821,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 */, @@ -4827,6 +4852,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 */, @@ -5064,6 +5090,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 */, @@ -5115,6 +5142,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..d2817da4 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->IsPureNeutralDES()) 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 0ad8c895..f3edab7a 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 @@ -1802,7 +1809,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 @@ -1814,7 +1821,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 , @@ -3790,6 +3797,38 @@ This method replaces the deprecated method \f4\fs20 , while providing additional useful options. It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(float)offsetForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the individual offset(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)phenotypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the individual phenotype(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 +\'a0(void)outputIndividuals([Ns$\'a0filePath\'a0=\'a0NULL], [logical$\'a0append\'a0=\'a0F], [Niso$\'a0chromosome\'a0=\'a0NULL], [logical$\'a0spatialPositions\'a0=\'a0T], [logical$\'a0ages\'a0=\'a0T], [logical$\'a0ancestralNucleotides\'a0=\'a0F], [logical$\'a0pedigreeIDs\'a0=\'a0F], [logical$\'a0objectTags\'a0=\'a0F])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -4148,6 +4187,68 @@ See also \f4\fs20 for a different metric of relatedness.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 +\'a0(void)setOffsetForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0offset\'a0=\'a0NULL])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the individual offset(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 offset +\f4\fs20 must follow one of four patterns. In the first pattern, +\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.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 +\'a0(void)setPhenotypeForTrait(Nio\'a0trait, numeric\'a0phenotype)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the individual phenotype(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 phenotype +\f4\fs20 must follow one of three patterns. In the first pattern, +\f3\fs18 phenotype +\f4\fs20 is a singleton value; this sets the given phenotype for each of the specified traits in each target individual. In the second pattern, +\f3\fs18 phenotype +\f4\fs20 is of length equal to the number of specified traits; this sets the phenotype for each of the specified traits to the corresponding phenotype in each target individual. In the third pattern, +\f3\fs18 phenotype +\f4\fs20 is of length equal to the number of specified traits times the number of target individuals; this uses +\f3\fs18 phenotype +\f4\fs20 to provide a different phenotype for each trait in each individual, using consecutive values from +\f3\fs18 phenotype +\f4\fs20 to set the phenotype for each of the specified traits in one individual before moving to the next individual.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \expnd0\expndtw0\kerning0 +\'a0(void)setSpatialPosition(float\'a0position)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -5890,6 +5991,87 @@ 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.\ +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 @@ -5983,42 +6165,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 @@ -6054,45 +6200,173 @@ 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([Nio\'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 -\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 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Nio\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\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 Returns the mutation\'92s effect size for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 ; for multiplicative traits, this is typically the selection coefficient +\f1\i s +\f4\i0 , whereas for additive traits it is typically the additive effect size +\f1\i a +\f4\i0 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 \'96\'a0(void)setSelectionCoeff(float$\'a0selectionCoeff) -\f5 \ +\f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the mutation\'92s hemizygous dominance coefficient for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 ; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient +\f1\i h +\f4\i0\fs13\fsmilli6667 \sub hemi +\fs20 \nosupersub . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 +\'a0(void)setDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the mutation\'92s dominance coefficient(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 dominance +\f4\fs20 must follow one of four patterns. In the first pattern, +\f3\fs18 dominance +\f4\fs20 is +\f3\fs18 NULL +\f4\fs20 ; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation. (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.) In the second pattern, +\f3\fs18 dominance +\f4\fs20 is a singleton value; this sets the given dominance for each of the specified traits in each target mutation. In the third pattern, +\f3\fs18 dominance +\f4\fs20 is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation. In the fourth pattern, +\f3\fs18 dominance +\f4\fs20 is of length equal to the number of specified traits times the number of target mutations; this uses +\f3\fs18 dominance +\f4\fs20 to provide a different dominance coefficient for each trait in each mutation, using consecutive values from +\f3\fs18 dominance +\f4\fs20 to set the dominance for each of the specified traits in one mutation before moving to the next mutation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 +\'a0(void)setEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0effect\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \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 Sets the mutation\'92s effect(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 effect +\f4\fs20 must follow one of four patterns. In the first pattern, +\f3\fs18 effect +\f4\fs20 is +\f3\fs18 NULL +\f4\fs20 ; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation. (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.) In the second pattern, +\f3\fs18 effect +\f4\fs20 is a singleton value; this sets the given effect for each of the specified traits in each target mutation. In the third pattern, +\f3\fs18 effect +\f4\fs20 is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation. In the fourth pattern, +\f3\fs18 effect +\f4\fs20 is of length equal to the number of specified traits times the number of target mutations; this uses +\f3\fs18 effect +\f4\fs20 to provide a different effect value for each trait in each mutation, using consecutive values from +\f3\fs18 effect +\f4\fs20 to set the effect for each of the specified traits in one mutation before moving to the next mutation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 +\'a0(void)setHemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the mutation\'92s hemizygous dominance coefficient(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 dominance +\f4\fs20 must follow one of four patterns. In the first pattern, +\f3\fs18 dominance +\f4\fs20 is +\f3\fs18 NULL +\f4\fs20 ; this sets the dominance for each of the specified traits to the default hemizygous dominance coefficient from the mutation type of the mutation in each target mutation. (Note that mutation hemizygous dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default hemizygous dominance values.) In the second pattern, +\f3\fs18 dominance +\f4\fs20 is a singleton value; this sets the given hemizygous dominance for each of the specified traits in each target mutation. In the third pattern, +\f3\fs18 dominance +\f4\fs20 is of length equal to the number of specified traits; this sets the hemizygous dominance for each of the specified traits to the corresponding dominance value in each target mutation. In the fourth pattern, +\f3\fs18 dominance +\f4\fs20 is of length equal to the number of specified traits times the number of target mutations; this uses +\f3\fs18 dominance +\f4\fs20 to provide a different hemizygous dominance coefficient for each trait in each mutation, using consecutive values from +\f3\fs18 dominance +\f4\fs20 to set the hemizygous dominance for each of the specified traits in one mutation before moving to the next mutation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)setMutationType(io$\'a0mutType)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\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\ @@ -6183,254 +6457,7 @@ SLiM consults this flag at the end of each tick when deciding whether to substit \f4\fs20 objects.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \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 @@ -6547,22 +6574,165 @@ The species to which the target object belongs.\ \f1\fs22 methods\ \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\i0\fs18 \cf2 \'96\'a0(float)defaultDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their +\f3\fs18 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([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the default hemizygous dominance coefficient used for the specified trait or traits, for mutations of this type when hemizygous (i.e., when present in only one copy because they are facing a null haplosome, such as for an X chromosome in a male). The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The default hemizygous dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their +\f3\fs18 hemizygousDominance +\f4\fs20 property, but that can be changed later with the +\f3\fs18 Mutation +\f4\fs20 method +\f3\fs18 setHemizygousDominanceForTrait() +\f4\fs20 .\ +Note that dominance coefficients are not bounded. A dominance coefficient greater than +\f3\fs18 1.0 +\f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ +Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision +\f3\fs18 float +\f4\fs20 , not the double-precision +\f3\fs18 float +\f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Draws and returns a vector of \f3\fs18 n -\f4\fs20 selection coefficients using the currently defined distribution of fitness effects (DFE) for the target mutation type. If the DFE is type +\f4\fs20 mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. See the +\f3\fs18 MutationType +\f4\fs20 class documentation for discussion of the supported distributions and their uses. If the distribution of effects is of type \f3\fs18 "s" -\f4\fs20 , this method will result in synchronous execution of the DFE\'92s script.\ -\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\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 \cf0 \kerning1\expnd0\expndtw0 \'96\'a0(void)setDistribution(string$\'a0distributionType, ...) -\f5 \ +\f3\fs18 \cf2 \'96\'a0(fs)effectDistributionParamsForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the parameters that configure the distribution of effects for the specified trait or traits. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The distribution parameters will be of type +\f3\fs18 string +\f4\fs20 for 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([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the type of distribution of effects for the specified trait or traits. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The distribution type will be one of +\f3\fs18 "f" +\f4\fs20 , +\f3\fs18 "g" +\f4\fs20 , +\f3\fs18 "e" +\f4\fs20 , +\f3\fs18 "n" +\f4\fs20 , +\f3\fs18 "p" +\f4\fs20 , +\f3\fs18 "w" +\f4\fs20 , or +\f3\fs18 "s" +\f4\fs20 , as discussed in the +\f3\fs18 MutationType +\f4\fs20 class documentation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)setDefaultDominanceForTrait(Nio\'a0trait, float\'a0dominance)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Set the default dominance coefficient for a specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The value of +\f3\fs18 dominance +\f4\fs20 must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of +\f3\fs18 defaultDominance +\f4\fs20 is used for each corresponding trait).\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)setDefaultHemizygousDominanceForTrait(Nio\'a0trait, float\'a0dominance)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Set the distribution of fitness effects for a mutation type. The +\f4\fs20 \cf2 Set the default hemizygous dominance coefficient for a specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The value of +\f3\fs18 dominance +\f4\fs20 must either be singleton (in which case it is set as the default hemizygous dominance for all specified traits), or must match the number of specified traits (in which case one element of +\f3\fs18 dominance +\f4\fs20 is used for each corresponding trait).\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)setEffectDistributionForTrait(Nio\'a0trait, string$\'a0distributionType, ...)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Set the distribution of effects for a specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species.\ +The \f3\fs18 distributionType \f4\fs20 may be \f3\fs18 "f" @@ -6604,7 +6774,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\ @@ -8937,6 +9109,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 @@ -10658,6 +10838,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 @@ -13455,7 +13663,7 @@ This method is similar to getting the \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 Set the migration rates to this subpopulation from the subpopulations in +\f4\fs20 \cf2 Set the migration rates to this subpopulation from the subpopulations in \f3\fs18 sourceSubpops \f4\fs20 to the corresponding rates specified in \f3\fs18 rates @@ -13463,14 +13671,26 @@ This method is similar to getting the \f3\fs18 rates \f4\fs20 gives the expected fractions of the children in this subpopulation that will subsequently be generated from parents in the subpopulations \f3\fs18 sourceSubpops -\f4\fs20 (see the SLiM manual for further details). This method will only set the migration fractions from the subpopulations given; migration rates from other subpopulations will be left unchanged (explicitly set a zero rate to turn off migration from a given subpopulation). The type of +\f4\fs20 (see section 24.2.1). The +\f3\fs18 rates +\f4\fs20 parameter may be a singleton value, in which case that rate is used for all subpopulations in +\f3\fs18 sourceSubpops +\f4\fs20 . This method will only set the migration fractions from the subpopulations given; migration rates from other subpopulations will be left unchanged (explicitly set a zero rate to turn off migration from a given subpopulation). The type of \f3\fs18 sourceSubpops \f4\fs20 may be either \f3\fs18 integer \f4\fs20 , specifying subpopulations by identifier, or \f3\fs18 object -\f4\fs20 , specifying subpopulations directly. -\f5 \ +\f4\fs20 , specifying subpopulations directly.\ +In general it is illegal to try to set the migration rate into a subpopulation from itself; that rate is, by definition, equal to the remainder after all migration from other subpopulations. As a special case for convenience, it is legal to set a rate of +\f3\fs18 0.0 +\f4\fs20 for all subpopulations in the species, including the target subpopulation. For example, +\f3\fs18 subpops.setMigrationRates(allSubpops, 0.0) +\f4\fs20 will turn off all migration into the subpopulations in +\f3\fs18 subpops +\f4\fs20 . The given rate of +\f3\fs18 0.0 +\f4\fs20 from a subpop into itself is simply ignored, for this specific case only.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 \'96\'a0(void)setSelfingRate(numeric$\'a0rate) @@ -13780,6 +14000,48 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 object with which the mutation is associated.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 dominance => (float)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The dominance coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\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 \cf2 The selection coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 effectForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\f3\fs18 Substitution +\f4\fs20 objects will have a dynamic property named +\f3\fs18 heightEffect +\f4\fs20 to access the effect for that trait.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 hemizygousDominance => (float)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The hemizygous dominance coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 hemizygousDominanceForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\f3\fs18 Substitution +\f4\fs20 objects will have a dynamic property named +\f3\fs18 heightHemizygousDominance +\f4\fs20 to access the dominance for that trait.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf0 id => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -13806,7 +14068,7 @@ Note that this method is only for use in nonWF models, in which migration is man \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \expnd0\expndtw0\kerning0 -nucleotide <\'96> (string$)\ +nucleotide => (string$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 A @@ -13822,7 +14084,7 @@ nucleotide <\'96> (string$)\ \f4\fs20 . If the mutation is not nucleotide-based, this property is unavailable.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 nucleotideValue <\'96> (integer$)\ +\f3\fs18 \cf2 nucleotideValue => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 An @@ -13852,14 +14114,6 @@ nucleotide <\'96> (string$)\ \f5 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 selectionCoeff => (float$)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf0 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its -\f3\fs18 MutationType -\f5\fs20 .\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - \f3\fs18 \cf0 subpopID <\'96> (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -13893,7 +14147,162 @@ nucleotide <\'96> (string$)\ \f1\i\fs22 \cf0 5.18.2 \f2\fs18 Substitution \f1\fs22 methods\ -\pard\pardeftab397\fi274\ri720\sb40\sa40\partightenfactor0 +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f5\i0 \cf0 \ +\f3\i0\fs18 \cf2 \'96\'a0(float)dominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the substitution\'92s dominance coefficient for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 , carried over from the original mutation object. For both multiplicative traits and additive traits this is the dominance coefficient +\f1\i h +\f4\i0 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 . +\f5\fs22 \cf0 \ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the substitution\'92s effect size for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 , carried over from the original mutation object. For multiplicative traits, this is typically the selection coefficient +\f1\i s +\f4\i0 , whereas for additive traits it is typically the additive effect size +\f1\i a +\f4\i0 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the substitution\'92s hemizygous dominance coefficient for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 , carried over from the original mutation object. For both multiplicative traits and additive traits this is the hemizygous dominance coefficient +\f1\i h +\f4\i0\fs13\fsmilli6667 \sub hemi +\fs20 \nosupersub . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 + +\f0\b\fs22 \cf2 5.19 Class Trait\ +\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.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 directFitnessEffect <\'96> (logical$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 A +\f3\fs18 logical +\f4\fs20 flag indicating whether the trait has a direct fitness effect or not. If +\f3\fs18 T +\f4\fs20 , the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component. If +\f3\fs18 F +\f4\fs20 , the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a \'93fitness function\'94), or the trait might have other effects that are not obviously related to fitness at all.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 index => (integer$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The index of the trait in the vector of traits kept by the species. The first trait defined in a species is at index +\f3\fs18 0 +\f4\fs20 , and subsequent traits count upwards from there. The index of a trait is often used to refer to the trait, so it is important. A global constant is defined for every trait, using each trait\'92s name, that provides the index of each trait, so this property will probably rarely be needed.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 individualOffsetMean <\'96> (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The mean for the normal distribution from which individual offsets are drawn. Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the +\f3\fs18 individualOffsetSD +\f4\fs20 property.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 individualOffsetSD <\'96> (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The standard deviation for the normal distribution from which individual offsets are drawn. Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the +\f3\fs18 individualOffsetMean +\f4\fs20 property.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 name => (string$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The name of the trait, as given to +\f3\fs18 initializeTrait() +\f4\fs20 . The default trait that is provided if no trait is explicitly defined has a name that is the name of the species plus a +\f3\fs18 T +\f4\fs20 ; so for a single-species model, the default trait will generally be named +\f3\fs18 simT +\f4\fs20 . The name of a trait can sometimes be used to refer to the trait, and is visible in SLiMgui.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 species => (object$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The species to which the target object belongs.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 tag <\'96> (integer$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 A user-defined +\f3\fs18 integer +\f4\fs20 value. The value of +\f3\fs18 tag +\f4\fs20 is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code. The value of +\f3\fs18 tag +\f4\fs20 is not used by SLiM; it is free for you to use.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 type => (string$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The type of the trait, as a +\f3\fs18 string +\f4\fs20 . In the present design, this will be either +\f3\fs18 "multiplicative" +\f4\fs20 or +\f3\fs18 "additive" +\f4\fs20 .\ +\pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 + +\f1\i\fs22 \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..bf5cf3b9 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,7 +862,9 @@ See \f1\fs18 dec \f2\fs20 , which are passed through to it; and see \f1\fs18 initializeMutationRate() -\f2\fs20 for details on how the rate map is validated and used.\ +\f2\fs20 for details on how the rate map is validated and used, and how the +\f1\fs18 sex +\f2\fs20 parameter is used.\ This function is written in Eidos, and its source code can be viewed with \f1\fs18 functionSource() \f2\fs20 , so you can copy and modify its code if you need to modify its functionality.\ @@ -882,15 +884,31 @@ 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 can subsequently be configured with the +\f1\fs18 setDefaultHemizygousDominanceForTrait() +\f2\fs20 method if desired.\ +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 +948,9 @@ 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. 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 @@ -1156,22 +1176,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 +1451,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 +1477,120 @@ 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 or +\f1\fs18 Species +\f2\fs20 classes. These requirements are necessary because, after the new trait is created, new properties added to the +\f1\fs18 Individual +\f2\fs20 and +\f1\fs18 Species +\f2\fs20 classes, with the same name as the new trait, for convenience. The new +\f1\fs18 Individual +\f2\fs20 property allows trait values to be accessed directly through a property; for example, if the new trait is named +\f1\fs18 heightT +\f2\fs20 , getting and setting an individual\'92s trait value would be possible through the property +\f1\fs18 individual.heightT +\f2\fs20 . The new +\f1\fs18 Species +\f2\fs20 property allows traits themselves to be accessed directly through a property; continuing the previous example, +\f1\fs18 sim.heightT +\f2\fs20 would provide the +\f1\fs18 Trait +\f2\fs20 object named +\f1\fs18 heightT +\f2\fs20 . If desired, +\f1\fs18 defineConstant() +\f2\fs20 may also be used to set up a global constant for a trait; for example, +\f1\fs18 defineConstant("heightT", heightT) +\f2\fs20 would allow the +\f1\fs18 Trait +\f2\fs20 object to be referenced simply as +\f1\fs18 heightT +\f2\fs20 . For clarity, it is suggested that trait names end in a +\f1\fs18 "T" +\f2\fs20 to differentiate them from other variables and properties, but this is not required.\ +The +\f1\fs18 type +\f2\fs20 parameter gives the type of trait to be created, as a +\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.\ +The +\f1\fs18 individualOffsetMean +\f2\fs20 and +\f1\fs18 individualOffsetSD +\f2\fs20 parameters together define a normal distribution from which individual offsets are drawn to provide what is often called \'93environmental variance\'94 or \'93developmental noise\'94. As for the baseline offset, the individual offset mean defaults (if +\f1\fs18 NULL +\f2\fs20 is passed) to +\f1\fs18 1.0 +\f2\fs20 for multiplicative traits, +\f1\fs18 0.0 +\f2\fs20 for additive traits, to produce no effect. The default standard deviation for the individual offset, if +\f1\fs18 NULL +\f2\fs20 is passed, is +\f1\fs18 0.0 +\f2\fs20 . If +\f1\fs18 NULL +\f2\fs20 is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.\ +Finally, the +\f1\fs18 directFitnessEffect +\f2\fs20 parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual. This will typically be +\f1\fs18 T +\f2\fs20 (the default) in population-genetics models where the product of all mutation effects ( +\f1\fs18 1+s +\f2\fs20 or +\f1\fs18 1+hs +\f2\fs20 for each mutation) is used as the fitness of the individual, but will typically be +\f1\fs18 F +\f2\fs20 in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function. It would also be +\f1\fs18 F +\f2\fs20 for any trait that affects an aspect of the individual other than fitness \'96 dispersal distance, for example, or aggression.\ +The use of the +\f1\fs18 initializeTrait() +\f2\fs20 function is optional. If it is not called, a new +\f1\fs18 Trait +\f2\fs20 object will be created automatically, with a name generated from the species name plus a +\f1\fs18 "T" +\f2\fs20 ; typically, then, the name is +\f1\fs18 simT +\f2\fs20 , except in multispecies models. This default trait is configured to be multiplicative, with default values for the other parameters except +\f1\fs18 directFitnessEffect +\f2\fs20 , which is +\f1\fs18 T +\f2\fs20 for the default trait. This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.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 +2787,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 +2811,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 +2820,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 +2991,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 +3019,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/VERSIONS b/VERSIONS index ff06bf75..57fc5649 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 @@ -23,6 +23,76 @@ development head (in the master branch): fix #580, add isClose() and allClose() Eidos functions for comparison within a given tolerance +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 a C++ IsPureNeutralDES() method to represent whether all of the effects of a given mutation type are all 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 + + 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) @@ -69,7 +139,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: @@ -132,7 +201,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..955b5a42 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 diff --git a/core/chromosome.h b/core/chromosome.h index d4dedcfa..044d3e92 100644 --- a/core/chromosome.h +++ b/core/chromosome.h @@ -198,6 +198,7 @@ class Chromosome : public EidosDictionaryRetained Community &community_; Species &species_; + MutationBlock *mutation_block_ = nullptr; // NOT OWNED; a pointer to the MutationBlock from the species // the total haplosome count depends on the chromosome; it will be different for an autosome versus a sex chromosome, for example slim_refcount_t total_haplosome_count_ = 0; // the number of non-null haplosomes in the population; a fixed mutation has this count diff --git a/core/community.cpp b/core/community.cpp index e08530f9..1abbdbcb 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 @@ -2558,23 +2559,28 @@ void Community::AllSpecies_CheckIntegrity(void) const MutationIndex *registry = species->population_.MutationRegistry(®istry_size); std::vector indices; - for (int registry_index = 0; registry_index < registry_size; ++registry_index) + if (registry_size) { - MutationIndex mutation_index = registry[registry_index]; + MutationIndex mutBlockCapacity = species->SpeciesMutationBlock()->capacity_; - if ((mutation_index < 0) || (mutation_index >= gSLiM_Mutation_Block_Capacity)) - EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) mutation index " << mutation_index << " out of the mutation block." << EidosTerminate(); + for (int registry_index = 0; registry_index < registry_size; ++registry_index) + { + MutationIndex mutation_index = registry[registry_index]; + + if ((mutation_index < 0) || (mutation_index >= mutBlockCapacity)) + EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) mutation index " << mutation_index << " out of the mutation block." << EidosTerminate(); + + indices.push_back(mutation_index); + } - indices.push_back(mutation_index); + size_t original_size = indices.size(); + + std::sort(indices.begin(), indices.end()); + indices.resize(static_cast(std::distance(indices.begin(), std::unique(indices.begin(), indices.end())))); + + if (indices.size() != original_size) + EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) duplicate mutation index in the mutation registry (size difference " << (original_size - indices.size()) << ")." << EidosTerminate(); } - - size_t original_size = indices.size(); - - std::sort(indices.begin(), indices.end()); - indices.resize(static_cast(std::distance(indices.begin(), std::unique(indices.begin(), indices.end())))); - - if (indices.size() != original_size) - EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) duplicate mutation index in the mutation registry (size difference " << (original_size - indices.size()) << ")." << EidosTerminate(); } #endif } @@ -3405,8 +3411,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_eidos.cpp b/core/community_eidos.cpp index 8599336d..30cab918 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -20,6 +20,7 @@ #include "community.h" +#include "trait.h" #include "haplosome.h" #include "individual.h" #include "subpopulation.h" @@ -88,6 +89,7 @@ EidosValue_SP Community::ContextDefinedFunctionDispatch(const std::string &p_fun else if (p_function_name.compare(gStr_initializeMutationType) == 0) return active_species_->ExecuteContextFunction_initializeMutationType(p_function_name, p_arguments, p_interpreter); // NOLINT(*-branch-clone) : intentional consecutive branches else if (p_function_name.compare(gStr_initializeMutationTypeNuc) == 0) return active_species_->ExecuteContextFunction_initializeMutationType(p_function_name, p_arguments, p_interpreter); else if (p_function_name.compare(gStr_initializeRecombinationRate) == 0) return active_species_->ExecuteContextFunction_initializeRecombinationRate(p_function_name, p_arguments, p_interpreter); + else if (p_function_name.compare(gStr_initializeTrait) == 0) return active_species_->ExecuteContextFunction_initializeTrait(p_function_name, p_arguments, p_interpreter); else if (p_function_name.compare(gStr_initializeChromosome) == 0) return active_species_->ExecuteContextFunction_initializeChromosome(p_function_name, p_arguments, p_interpreter); else if (p_function_name.compare(gStr_initializeGeneConversion) == 0) return active_species_->ExecuteContextFunction_initializeGeneConversion(p_function_name, p_arguments, p_interpreter); else if (p_function_name.compare(gStr_initializeMutationRate) == 0) return active_species_->ExecuteContextFunction_initializeMutationRate(p_function_name, p_arguments, p_interpreter); @@ -123,6 +125,7 @@ const std::vector *Community::ZeroTickFunctionSignat ->AddIntString_S("id")->AddNumeric_S("dominanceCoeff")->AddString_S("distributionType")->AddEllipsis()); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeRecombinationRate, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric("rates")->AddInt_ON("ends", gStaticEidosValueNULL)->AddString_OS("sex", gStaticEidosValue_StringAsterisk)); + sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeTrait, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class, "SLiM"))->AddString_S("name")->AddString_S("type")->AddFloat_OSN("baselineOffset", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetMean", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetSD", gStaticEidosValueNULL)->AddLogical_OS("directFitnessEffect", gStaticEidosValue_LogicalF)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeChromosome, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class, "SLiM"))->AddInt_S("id")->AddInt_OSN("length", gStaticEidosValueNULL)->AddString_OS("type", gStaticEidosValue_StringA)->AddString_OSN("symbol", gStaticEidosValueNULL)->AddString_OSN("name", gStaticEidosValueNULL)->AddInt_OS("mutationRuns", gStaticEidosValue_Integer0)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeGeneConversion, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric_S("nonCrossoverFraction")->AddNumeric_S("meanLength")->AddNumeric_S("simpleConversionFraction")->AddNumeric_OS("bias", gStaticEidosValue_Integer0)->AddLogical_OS("redrawLengthsOnFailure", gStaticEidosValue_LogicalF)); @@ -385,6 +388,17 @@ EidosValue_SP Community::GetProperty(EidosGlobalStringID p_property_id) return result_SP; } + case gID_allTraits: + { + EidosValue_Object *vec = new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Trait_Class); + EidosValue_SP result_SP = EidosValue_SP(vec); + + for (auto species : all_species_) + for (auto trait : species->Traits()) + vec->push_object_element_RR(trait); + + return result_SP; + } case gID_logFiles: { EidosValue_Object *vec = new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_LogFile_Class); @@ -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 @@ -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..bec96162 100644 --- a/core/genomic_element.cpp +++ b/core/genomic_element.cpp @@ -101,8 +101,9 @@ EidosValue_SP GenomicElement::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *GenomicElement::GetProperty_Accelerated_startPosition(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElement::GetProperty_Accelerated_startPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -115,8 +116,9 @@ EidosValue *GenomicElement::GetProperty_Accelerated_startPosition(EidosObject ** return int_result; } -EidosValue *GenomicElement::GetProperty_Accelerated_endPosition(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElement::GetProperty_Accelerated_endPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -129,8 +131,9 @@ EidosValue *GenomicElement::GetProperty_Accelerated_endPosition(EidosObject **p_ return int_result; } -EidosValue *GenomicElement::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElement::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -147,8 +150,9 @@ EidosValue *GenomicElement::GetProperty_Accelerated_tag(EidosObject **p_values, return int_result; } -EidosValue *GenomicElement::GetProperty_Accelerated_genomicElementType(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElement::GetProperty_Accelerated_genomicElementType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Object *object_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_GenomicElementType_Class))->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) diff --git a/core/genomic_element.h b/core/genomic_element.h index d40c5db1..8b67e05a 100644 --- a/core/genomic_element.h +++ b/core/genomic_element.h @@ -76,10 +76,10 @@ class GenomicElement : public EidosObject EidosValue_SP ExecuteMethod_setGenomicElementType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_startPosition(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_endPosition(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_genomicElementType(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_startPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_endPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_genomicElementType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; // support stream output of GenomicElement, for debugging diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index 12e90a7e..cffe51fc 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,8 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationFractions(EidosGlobal mutation_fractions.emplace_back(proportion); // check whether we are now using a mutation type that is non-neutral; check and set pure_neutral_ - if ((mutation_type_ptr->dfe_type_ != DFEType::kFixed) || (mutation_type_ptr->dfe_parameters_[0] != 0.0)) + if (!mutation_type_ptr->IsPureNeutralDES()) + //if ((mutation_type_ptr->des_type_ != DESType::kFixed) || (mutation_type_ptr->des_parameters_[0] != 0.0)) species_.pure_neutral_ = false; } diff --git a/core/genomic_element_type.h b/core/genomic_element_type.h index fca94114..4c2300a1 100644 --- a/core/genomic_element_type.h +++ b/core/genomic_element_type.h @@ -102,8 +102,8 @@ class GenomicElementType : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_setMutationMatrix(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; // support stream output of GenomicElementType, for debugging diff --git a/core/haplosome.cpp b/core/haplosome.cpp index c910034b..6a33e51e 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -26,6 +26,7 @@ #include "species.h" #include "polymorphism.h" #include "subpopulation.h" +#include "mutation_block.h" #include "eidos_sorting.h" #include @@ -334,7 +335,7 @@ void Haplosome::record_derived_states(Species *p_species) const // This is called by Species::RecordAllDerivedStatesFromSLiM() to record all the derived states present // in a given haplosome that was just created by readFromPopulationFile() or some similar situation. It should // make calls to record the derived state at each position in the haplosome that has any mutation. - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = p_species->SpeciesMutationBlock()->mutation_buffer_; THREAD_SAFETY_IN_ACTIVE_PARALLEL("Haplosome::record_derived_states(): usage of statics"); @@ -445,7 +446,7 @@ EidosValue_SP Haplosome::GetProperty(EidosGlobalStringID p_property_id) if (IsDeferred()) EIDOS_TERMINATION << "ERROR (Haplosome::GetProperty): the mutations of deferred haplosomes cannot be accessed." << EidosTerminate(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = individual_->subpopulation_->species_.SpeciesMutationBlock()->mutation_buffer_; int mut_count = mutation_count(); EidosValue_Object *vec = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Mutation_Class))->resize_no_initialize_RR(mut_count); EidosValue_SP result_SP = EidosValue_SP(vec); @@ -481,8 +482,9 @@ EidosValue_SP Haplosome::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *Haplosome::GetProperty_Accelerated_haplosomePedigreeID(EidosObject **p_values, size_t p_values_size) +EidosValue *Haplosome::GetProperty_Accelerated_haplosomePedigreeID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); size_t value_index = 0; @@ -508,8 +510,9 @@ EidosValue *Haplosome::GetProperty_Accelerated_haplosomePedigreeID(EidosObject * return int_result; } -EidosValue *Haplosome::GetProperty_Accelerated_chromosomeSubposition(EidosObject **p_values, size_t p_values_size) +EidosValue *Haplosome::GetProperty_Accelerated_chromosomeSubposition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -523,8 +526,9 @@ EidosValue *Haplosome::GetProperty_Accelerated_chromosomeSubposition(EidosObject return int_result; } -EidosValue *Haplosome::GetProperty_Accelerated_isNullHaplosome(EidosObject **p_values, size_t p_values_size) +EidosValue *Haplosome::GetProperty_Accelerated_isNullHaplosome(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -537,8 +541,9 @@ EidosValue *Haplosome::GetProperty_Accelerated_isNullHaplosome(EidosObject **p_v return logical_result; } -EidosValue *Haplosome::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *Haplosome::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -575,8 +580,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 @@ -847,7 +853,7 @@ EidosValue_SP Haplosome::ExecuteMethod_Accelerated_countOfMutationsOfType(EidosO // Count the number of mutations of the given type const int32_t mutrun_count = ((Haplosome *)(p_elements[0]))->mutrun_count_; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species->SpeciesMutationBlock()->mutation_buffer_; EidosValue_Int *integer_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_elements_size); bool saw_error = false; @@ -902,7 +908,7 @@ EidosValue_SP Haplosome::ExecuteMethod_mutationsOfType(EidosGlobalStringID p_met // We want to return a singleton if we can, but we also want to avoid scanning through all our mutations twice. // We do this by not creating a vector until we see the second match; with one match, we make a singleton. - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; Mutation *first_match = nullptr; EidosValue_Object *vec = nullptr; EidosValue_SP result_SP; @@ -1258,7 +1264,7 @@ EidosValue_SP Haplosome::ExecuteMethod_positionsOfMutationsOfType(EidosGlobalStr // Return the positions of mutations of the given type EidosValue_Int *int_result = new (gEidosValuePool->AllocateChunk()) EidosValue_Int(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; for (int run_index = 0; run_index < mutrun_count_; ++run_index) { @@ -1285,6 +1291,8 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *mutType_value = p_arguments[0].get(); + // FIXME MULTITRAIT: This should perhaps take a trait as its second parameter, so that it can be used with any trait; and its doc needs to be rewritten; and it should be deprecated + if (IsDeferred()) EIDOS_TERMINATION << "ERROR (Haplosome::ExecuteMethod_sumOfMutationsOfType): the mutations of deferred haplosomes cannot be accessed." << EidosTerminate(); if (IsNull()) @@ -1294,8 +1302,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) @@ -1304,22 +1313,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 += 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 @@ -1411,9 +1424,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 @@ -1681,7 +1694,8 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i Species &species = p_chromosome.species_; bool nucleotide_based = species.IsNucleotideBased(); NucleotideArray *ancestral_seq = p_chromosome.AncestralSequence(); - Mutation *mut_block_ptr = 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 @@ -1946,7 +1960,12 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i { if (polymorphism != nuc_based.front()) p_out << ','; - p_out << polymorphism->mutation_ptr_->selection_coeff_; + + // FIXME MULTITRAIT: need to write out all trait info, not just trait 0 + const Mutation *mut = polymorphism->mutation_ptr_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + p_out << mut_trait_info[0].effect_size_; } p_out << ";"; @@ -1955,7 +1974,12 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i { if (polymorphism != nuc_based.front()) p_out << ','; - p_out << polymorphism->mutation_ptr_->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); + + p_out << mut_trait_info[0].dominance_coeff_; } p_out << ";"; @@ -2078,9 +2102,12 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i p_out << "\t1000\tPASS\t"; // emit the INFO fields and the Genotype marker + // FIXME MULTITRAIT: need to write out all trait info, not just trait 0 + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + p_out << "MID=" << mutation->mutation_id_ << ";"; - p_out << "S=" << mutation->selection_coeff_ << ";"; - p_out << "DOM=" << mutation->mutation_type_ptr_->dominance_coeff_ << ";"; + p_out << "S=" << mut_trait_info->effect_size_ << ";"; + p_out << "DOM=" << mut_trait_info->dominance_coeff_ << ";"; p_out << "PO=" << mutation->subpop_index_ << ";"; p_out << "TO=" << mutation->origin_tick_ << ";"; p_out << "MT=" << mutation->mutation_type_ptr_->mutation_type_id_ << ";"; @@ -2211,7 +2238,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)); @@ -2277,6 +2304,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. @@ -2510,12 +2539,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_pure_neutral_DES_; the mutation is already in the system } } } @@ -2542,7 +2571,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)); } } @@ -2550,7 +2579,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 { @@ -2561,7 +2590,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()); @@ -2583,6 +2612,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. @@ -2671,7 +2702,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(); @@ -2681,14 +2712,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)); @@ -2799,7 +2830,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // for the singleton case for each of the parameters, get all the info MutationType *singleton_mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(arg_muttype, 0, &community, species, method_name.c_str()); // SPECIES CONSISTENCY CHECK - double singleton_selection_coeff = (arg_selcoeff ? arg_selcoeff->NumericAtIndex_NOCAST(0, nullptr) : 0.0); + slim_effect_t singleton_selection_coeff = (arg_effect ? (slim_effect_t)arg_effect->NumericAtIndex_NOCAST(0, nullptr) : 0.0); slim_position_t singleton_position = SLiMCastToPositionTypeOrRaise(arg_position->IntAtIndex_NOCAST(0, nullptr)); @@ -2854,7 +2885,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // It is possible that some mutations will not actually be added to any haplosome, due to stacking; they will be cleared from the // registry as lost mutations in the next cycle. All mutations are returned to the user, whether actually added or not. MutationType *mutation_type_ptr = singleton_mutation_type_ptr; - double selection_coeff = singleton_selection_coeff; slim_position_t position = singleton_position; slim_objectid_t origin_subpop_id = singleton_origin_subpop_id; int64_t nucleotide = singleton_nucleotide; @@ -2870,14 +2900,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID if (muttype_count != 1) mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(arg_muttype, mut_parameter_index, &community, species, method_name.c_str()); // SPECIES CONSISTENCY CHECK - if (selcoeff_count != 1) - { - if (arg_selcoeff) - selection_coeff = arg_selcoeff->NumericAtIndex_NOCAST(mut_parameter_index, nullptr); - else - selection_coeff = mutation_type_ptr->DrawSelectionCoefficient(); - } - if (origin_subpop_count != 1) { if (arg_origin_subpop->Type() == EidosValueType::kValueInt) @@ -2895,16 +2917,39 @@ 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) + { + new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, origin_subpop_id, origin_tick, (int8_t)nucleotide); + + // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ + if (!mutation_type_ptr->all_pure_neutral_DES_) + species->pure_neutral_ = false; + } + else // (p_method_id == gID_addNewMutation) { - species->pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = false; + 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... + new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); + + // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ + // The selection coefficient was supplied by the user (i.e., not be from the mutation type's DES), so we set all_pure_neutral_DES_ also + if (selection_coeff != 0.0) + { + species->pure_neutral_ = false; + mutation_type_ptr->all_pure_neutral_DES_ = false; + } } // add to the registry, return value, haplosome, etc. @@ -2918,11 +2963,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); @@ -2940,7 +2981,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 @@ -2955,12 +2996,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; } } @@ -3158,9 +3199,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); } @@ -3193,10 +3234,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); @@ -3254,6 +3295,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(); @@ -3380,7 +3424,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr for (int mut_index = 0; mut_index < segsites; ++mut_index) { slim_position_t position = positions[mut_index]; - double selection_coeff = mutation_type_ptr->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; @@ -3396,17 +3440,18 @@ 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... + 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) { 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_pure_neutral_DES_ + //mutation_type_ptr->all_pure_neutral_DES_ = false; } // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry @@ -3415,7 +3460,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_;}); @@ -3468,10 +3512,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)); } } } @@ -3508,6 +3552,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); @@ -3721,8 +3768,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; @@ -3759,7 +3806,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 { @@ -3813,7 +3860,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(); @@ -3949,20 +3996,21 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if (!mutation_type_ptr) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file MT field missing, but no default mutation type was supplied in the mutationType parameter." << EidosTerminate(); - // check the dominance coefficient of DOM against that of the mutation type + // get the dominance coefficient from DOM, or use the default coefficient from the mutation type + slim_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 - // 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; @@ -4036,7 +4084,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) @@ -4044,20 +4092,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 + // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DES), so we set all_pure_neutral_DES_ also if (selection_coeff != 0.0) { species->pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = false; + mutation_type_ptr->all_pure_neutral_DES_ = false; } // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry @@ -4097,16 +4146,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); @@ -4124,7 +4172,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) @@ -4136,6 +4183,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(); @@ -4310,6 +4360,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID // Construct a vector of mutations to remove that is sorted by position std::vector mutations_to_remove; Mutation * const *mutations_data = (Mutation * const *)mutations_value->ObjectData(); + int trait_count = species->TraitCount(); for (int value_index = 0; value_index < mutations_count; ++value_index) { @@ -4323,8 +4374,20 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID mutations_to_remove.emplace_back(mut); - if (mut->selection_coeff_ != 0.0) - any_nonneutral_removed = true; + // If we're not already aware of having removed a non-neutral mutation, check on that now + if (!any_nonneutral_removed) + { + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (mut_trait_info[trait_index].effect_size_ != 0.0) + { + any_nonneutral_removed = true; + break; + } + } + } } std::sort(mutations_to_remove.begin(), mutations_to_remove.end(), [ ](Mutation *i1, Mutation *i2) {return i1->position_ < i2->position_;}); @@ -4469,7 +4532,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; } } @@ -4527,7 +4590,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) { @@ -4580,7 +4643,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)); } } } @@ -4617,6 +4680,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... @@ -4639,7 +4707,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) @@ -4677,7 +4745,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; @@ -4710,7 +4778,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; @@ -4745,8 +4813,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; @@ -4786,7 +4854,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..7df2b7f2 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -62,6 +62,7 @@ class Population; class Subpopulation; class Individual; class HaplosomeWalker; +class MutationBlock; extern EidosClass *gSLiM_Haplosome_Class; @@ -284,7 +285,7 @@ class Haplosome : public EidosObject static void BulkOperationEnd(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index); // Remove all mutations in p_haplosome that have a state_ of MutationState::kFixedAndSubstituted, indicating that they have fixed - void RemoveFixedMutations(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index) + inline __attribute__((always_inline)) void RemoveFixedMutations(Mutation *p_mut_block_ptr, int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index) { #if DEBUG if (mutrun_count_ == 0) @@ -296,7 +297,7 @@ class Haplosome : public EidosObject // Population::RemoveAllFixedMutations() for further context on this. MutationRun *mutrun = const_cast(mutruns_[p_mutrun_index]); - mutrun->RemoveFixedMutations(p_operation_id); + mutrun->RemoveFixedMutations(p_mut_block_ptr, p_operation_id); } // TallyHaplosomeReferences_Checkback() counts up the total MutationRun references, using their usage counts, as a checkback @@ -392,20 +393,20 @@ class Haplosome : public EidosObject // subpop_ = p_source_haplosome.subpop_; } - inline const std::vector *derived_mutation_ids_at_position(slim_position_t p_position) const + inline const std::vector *derived_mutation_ids_at_position(Mutation *p_mut_block_ptr, slim_position_t p_position) const { slim_mutrun_index_t run_index = (slim_mutrun_index_t)(p_position / mutrun_length_); - return mutruns_[run_index]->derived_mutation_ids_at_position(p_position); + return mutruns_[run_index]->derived_mutation_ids_at_position(p_mut_block_ptr, p_position); } void record_derived_states(Species *p_species) const; // print the sample represented by haplosomes, using SLiM's own format - static void PrintHaplosomes_SLiM(std::ostream &p_out, std::vector &p_haplosomes, bool p_output_object_tags); + static void PrintHaplosomes_SLiM(std::ostream &p_out, Species &p_species, std::vector &p_haplosomes, bool p_output_object_tags); // print the sample represented by haplosomes, using "ms" format - static void PrintHaplosomes_MS(std::ostream &p_out, std::vector &p_haplosomes, const Chromosome &p_chromosome, bool p_filter_monomorphic); + static void PrintHaplosomes_MS(std::ostream &p_out, Species &p_species, std::vector &p_haplosomes, const Chromosome &p_chromosome, bool p_filter_monomorphic); // print the sample represented by haplosomes, using "vcf" format static void PrintHaplosomes_VCF(std::ostream &p_out, std::vector &p_haplosomes, const Chromosome &p_chromosome, bool groupAsIndividuals, bool p_output_multiallelics, bool p_simplify_nucs, bool p_output_nonnucs); @@ -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 99a6c3a4..5d5f54c0 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,6 +54,7 @@ bool Individual::s_any_individual_fitness_scaling_set_ = false; // individual first, haplosomes later; this is the new multichrom paradigm // BCH 10/12/2024: Note that this will rarely be called after simulation startup; see NewSubpopIndividual() +// BCH 10/12/2025: Note also that NewSubpopIndividual() will rarely be called in WF models; see the Munge...() methods Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age) : #ifdef SLIMGUI color_set_(false), @@ -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_ = 0.0; + trait_info_0_.offset_ = traits[0]->DrawIndividualOffset(); + } + else if (trait_count == 0) + { +#if DEBUG + // If there is existing trait info, the number of traits should not have changed, so we should not need to adjust + if (trait_info_) + { + if (trait_info_ != &trait_info_0_) + free(trait_info_); + std::cout << "_InitializePerTraitInformation(): (internal error) unmatched trait info! (case 2)" << std::endl; + } +#endif + + trait_info_ = nullptr; + } + else + { +#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_ = 0.0; + 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) @@ -1407,7 +1496,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()) { @@ -1665,12 +1754,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(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) @@ -1683,8 +1786,9 @@ EidosValue *Individual::GetProperty_Accelerated_index(EidosObject **p_values, si return int_result; } -EidosValue *Individual::GetProperty_Accelerated_pedigreeID(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_pedigreeID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); size_t value_index = 0; @@ -1710,8 +1814,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) @@ -1728,8 +1833,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(); @@ -1745,8 +1851,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(); @@ -1762,8 +1869,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) @@ -1780,8 +1888,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) @@ -1797,8 +1906,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) @@ -1814,8 +1924,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) @@ -1831,8 +1942,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) @@ -1848,8 +1960,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) @@ -1865,8 +1978,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) @@ -1879,8 +1993,9 @@ EidosValue *Individual::GetProperty_Accelerated_migrant(EidosObject **p_values, return logical_result; } -EidosValue *Individual::GetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1893,8 +2008,9 @@ EidosValue *Individual::GetProperty_Accelerated_fitnessScaling(EidosObject **p_v return float_result; } -EidosValue *Individual::GetProperty_Accelerated_x(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_x(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1907,8 +2023,9 @@ EidosValue *Individual::GetProperty_Accelerated_x(EidosObject **p_values, size_t return float_result; } -EidosValue *Individual::GetProperty_Accelerated_y(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_y(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1921,8 +2038,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) @@ -1935,8 +2053,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; @@ -2018,8 +2137,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) @@ -2035,8 +2155,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 @@ -2103,8 +2224,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 @@ -2175,8 +2297,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 @@ -2243,8 +2366,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 @@ -2315,8 +2439,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 @@ -2345,8 +2470,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 @@ -2375,6 +2501,41 @@ EidosValue *Individual::GetProperty_Accelerated_haplosomesNonNull(EidosObject ** return object_result; } +EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) +{ +#pragma unused (p_property_id) + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); + const Individual **individuals_buffer = (const Individual **)p_values; + Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); + + if (species) + { + Trait *trait = species->TraitFromStringID(p_property_id); + int64_t trait_index = trait->Index(); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + + float_result->set_float_no_check(value->trait_info_[trait_index].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(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 @@ -2493,12 +2654,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 @@ -2518,8 +2694,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 @@ -2539,8 +2716,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(); @@ -2570,8 +2748,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(); @@ -2600,8 +2779,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(); @@ -2630,8 +2810,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(); @@ -2660,8 +2841,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(); @@ -2732,8 +2914,9 @@ bool Individual::_SetFitnessScaling_N(const double *source_data, EidosObject **p return saw_error; } -void Individual::SetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) Individual::s_any_individual_fitness_scaling_set_ = true; bool needs_raise = false; @@ -2754,8 +2937,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); @@ -2772,8 +2956,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); @@ -2790,8 +2975,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); @@ -2808,9 +2994,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) @@ -2865,8 +3051,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); @@ -2884,6 +3071,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) @@ -2891,6 +3141,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); @@ -2998,7 +3250,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(); @@ -3071,7 +3323,75 @@ EidosValue_SP Individual::ExecuteMethod_haplosomesForChromosomes(EidosGlobalStri return EidosValue_SP(vec); } + +// ********************* - (float)offsetForTrait([Nio trait = NULL]) +// +EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#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(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(offset); + } + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float)phenotypeForTrait([Nio trait = NULL]) +// +EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = subpopulation_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "phenotypeForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t phenotype = trait_info_[trait_index].phenotype_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(phenotype)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t phenotype = trait_info_[trait_index].phenotype_; + + float_result->push_float_no_check(phenotype); + } + + return EidosValue_SP(float_result); + } +} + // ********************* - (float)relatedness(object individuals, [Niso$ chromosome = NULL]) // EidosValue_SP Individual::ExecuteMethod_relatedness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -3189,6 +3509,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); @@ -3201,7 +3523,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(); @@ -3210,7 +3533,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++) { @@ -3226,18 +3549,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 += 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); @@ -3310,7 +3637,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()) { @@ -3582,7 +3909,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) { @@ -3920,6 +4247,10 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_countOfMutationsOfType, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_countOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_relatedness, kEidosValueMaskFloat))->AddObject("individuals", gSLiM_Individual_Class)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_haplosomesForChromosomes, kEidosValueMaskObject, gSLiM_Haplosome_Class))->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("index", gStaticEidosValueNULL)->AddLogical_OS("includeNulls", gStaticEidosValue_LogicalT)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddNumeric("phenotype")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_uniqueMutationsOfType, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->MarkDeprecated()); @@ -3940,6 +4271,8 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ { switch (p_method_id) { + case gID_setOffsetForTrait: return ExecuteMethod_setOffsetForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setPhenotypeForTrait: return ExecuteMethod_setPhenotypeForTrait(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividuals: return ExecuteMethod_outputIndividuals(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividualsToVCF: return ExecuteMethod_outputIndividualsToVCF(p_method_id, p_target, p_arguments, p_interpreter); case gID_readIndividualsFromVCF: return ExecuteMethod_readIndividualsFromVCF(p_method_id, p_target, p_arguments, p_interpreter); @@ -3956,6 +4289,276 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ } } +// ********************* + (void)setOffsetForTrait([Nio trait = NULL], [Nif offset = NULL]) +// +EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *offset_value = p_arguments[1].get(); + + int individuals_count = p_target->Count(); + int offset_count = offset_value->Count(); + + if (individuals_count == 0) + return gStaticEidosValueVOID; + + Individual **individuals_buffer = (Individual **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForIndividuals(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires that all individuals belong to the same species." << EidosTerminate(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setOffsetForTrait"); + int trait_count = (int)trait_indices.size(); + + if (offset_value->Type() == EidosValueType::kValueNULL) + { + // pattern 1: drawing a default offset value for each trait in one or more individuals + for (int64_t trait_index : trait_indices) + { + Trait *trait = species->Traits()[trait_index]; + slim_effect_t offset = trait->DrawIndividualOffset(); + + 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 if (offset_count == 1) + { + // pattern 2: setting a single offset value across one or more traits in one or more individuals + slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(0, nullptr)); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].offset_ = offset; + } + } + } + else if (offset_count == trait_count) + { + // pattern 3: setting one offset value per trait, in one or more individuals + int offset_index = 0; + + for (int64_t trait_index : trait_indices) + { + slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + ind->trait_info_[trait_index].offset_ = offset; + } + } + } + else if (offset_count == trait_count * individuals_count) + { + // pattern 4: setting different offset values for each trait in each individual; in this case, + // all offsets for the specified traits in a given individual are given consecutively + if (offset_value->Type() == EidosValueType::kValueInt) + { + // integer offset values + const int64_t *offsets_int = offset_value->IntData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = static_cast(*(offsets_int++)); + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].offset_ = static_cast(*(offsets_int++)); + } + } + } + else + { + // float offset values + const double *offsets_float = offset_value->FloatData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = static_cast(*(offsets_float++)); + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].offset_ = static_cast(*(offsets_float++)); + } + } + } + } + else + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires that offset be (a) NULL, requesting the default offset value for each trait, (b) singleton, providing one offset value for all traits, (c) equal in length to the number of traits in the species, providing one offset value per trait, or (d) equal in length to the number of traits times the number of target individuals, providing one offset value per trait per individual." << EidosTerminate(); + + return gStaticEidosValueVOID; +} + +// ********************* + (void)setPhenotypeForTrait([Nio trait = NULL], [Nif phenotype = NULL]) +// +EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *phenotype_value = p_arguments[1].get(); + + int individuals_count = p_target->Count(); + int phenotype_count = phenotype_value->Count(); + + if (individuals_count == 0) + return gStaticEidosValueVOID; + + Individual **individuals_buffer = (Individual **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForIndividuals(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires that all individuals belong to the same species." << EidosTerminate(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setPhenotypeForTrait"); + int trait_count = (int)trait_indices.size(); + + if (phenotype_count == 1) + { + // pattern 1: setting a single phenotype value across one or more traits in one or more individuals + slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(0, nullptr)); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = phenotype; + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].phenotype_ = phenotype; + } + } + } + else if (phenotype_count == trait_count) + { + // pattern 2: setting one phenotype value per trait, in one or more individuals + int phenotype_index = 0; + + for (int64_t trait_index : trait_indices) + { + slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(phenotype_index++, nullptr)); + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + ind->trait_info_[trait_index].phenotype_ = phenotype; + } + } + } + else if (phenotype_count == trait_count * individuals_count) + { + // pattern 3: setting different phenotype values for each trait in each individual; in this case, + // all phenotypes for the specified traits in a given individual are given consecutively + if (phenotype_value->Type() == EidosValueType::kValueInt) + { + // integer phenotype values + const int64_t *phenotypes_int = phenotype_value->IntData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); + } + } + } + else + { + // float phenotype values + const double *phenotypes_float = phenotype_value->FloatData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_float++)); + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_float++)); + } + } + } + } + else + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires that phenotype be (a) singleton, providing one phenotype for all traits, (b) equal in length to the number of traits in the species, providing one phenotype per trait, or (c) equal in length to the number of traits times the number of target individuals, providing one phenotype per trait per individual." << EidosTerminate(); + + return gStaticEidosValueVOID; +} + // ********************* + (void)outputIndividuals([Ns$ filePath = NULL], [logical$ append=F], [Niso$ chromosome = NULL], [logical$ spatialPositions = T], [logical$ ages = T], [logical$ ancestralNucleotides = F], [logical$ pedigreeIDs = F], [logical$ objectTags = F]) // EidosValue_SP Individual_Class::ExecuteMethod_outputIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const @@ -4135,7 +4738,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; @@ -4161,10 +4764,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]) @@ -4187,6 +4790,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(); @@ -4453,8 +5058,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; @@ -4491,7 +5096,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 { @@ -4545,7 +5150,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(); @@ -4578,20 +5183,21 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (!mutation_type_ptr) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file MT field missing, but no default mutation type was supplied in the mutationType parameter." << EidosTerminate(); - // check the dominance coefficient of DOM against that of the mutation type + // get the dominance coefficient from DOM, or use the default coefficient from the mutation type + slim_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 - // 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; @@ -4665,7 +5271,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) @@ -4673,19 +5279,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) { species->pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = false; + mutation_type_ptr->all_pure_neutral_DES_ = false; } // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry @@ -4898,14 +5505,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 @@ -4919,7 +5526,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 @@ -4939,7 +5546,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 @@ -4951,7 +5558,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 @@ -4968,7 +5575,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); @@ -5214,5 +5820,6 @@ EidosValue_SP Individual_Class::ExecuteMethod_setSpatialPosition(EidosGlobalStri + diff --git a/core/individual.h b/core/individual.h index b337c883..6f3d5d33 100644 --- a/core/individual.h +++ b/core/individual.h @@ -63,6 +63,14 @@ inline slim_pedigreeid_t SLiM_GetNextPedigreeID_Block(int p_block_size) return block_base; } +// This struct contains all information for a single trait in a single individual. In a multitrait +// model, each individual has a pointer to a buffer of these records, providing per-trait information. +typedef struct _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. @@ -143,13 +151,19 @@ class Individual : public EidosDictionaryUnretained // that confuses interpretation; note that individual_cached_fitness_OVERRIDE_ is not relevant to this #endif - Haplosome *hapbuffer_[2]; // *(hapbuffer_[2]), an internal buffer used to avoid allocation and increase memory nonlocality + Haplosome *hapbuffer_[2]; // *(hapbuffer_[2]), an internal buffer used to avoid allocation and increase memory locality Haplosome **haplosomes_; // OWNED haplosomes; can point to hapbuffer_ or to an external malloced block slim_age_t age_; // nonWF only: the age of the individual, in cycles; -1 in WF models slim_popsize_t index_; // the individual index in that subpop (0-based, and not multiplied by 2) Subpopulation *subpopulation_; // the subpop to which we belong; cannot be a reference because it changes on migration! + // 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_; @@ -164,6 +178,8 @@ class Individual : public EidosDictionaryUnretained Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age); virtual ~Individual(void) override; + void _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 +339,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 +348,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. @@ -402,6 +422,8 @@ 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; diff --git a/core/interaction_type.cpp b/core/interaction_type.cpp index a3e77c3c..2c981daf 100755 --- a/core/interaction_type.cpp +++ b/core/interaction_type.cpp @@ -3528,8 +3528,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) @@ -3542,8 +3543,9 @@ EidosValue *InteractionType::GetProperty_Accelerated_id(EidosObject **p_values, return int_result; } -EidosValue *InteractionType::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *InteractionType::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) diff --git a/core/interaction_type.h b/core/interaction_type.h index aa7cf2fe..5d6de121 100644 --- a/core/interaction_type.h +++ b/core/interaction_type.h @@ -469,8 +469,8 @@ class InteractionType : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_unevaluate(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; class InteractionType_Class : public EidosDictionaryUnretained_Class diff --git a/core/mutation.cpp b/core/mutation.cpp index dfd4085c..0e2872bc 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -21,7 +21,9 @@ #include "mutation.h" #include "eidos_call_signature.h" #include "eidos_property_signature.h" +#include "community.h" #include "species.h" +#include "mutation_block.h" #include #include @@ -30,296 +32,304 @@ #include -// All Mutation objects get allocated out of a single shared block, for speed; see SLiM_WarmUp() -// Note this is shared by all species; the mutations for every species come out of the same shared block. -Mutation *gSLiM_Mutation_Block = nullptr; -MutationIndex gSLiM_Mutation_Block_Capacity = 0; -MutationIndex gSLiM_Mutation_FreeIndex = -1; -MutationIndex gSLiM_Mutation_Block_LastUsedIndex = -1; - -#ifdef DEBUG_LOCKS_ENABLED -EidosDebugLock gSLiM_Mutation_LOCK("gSLiM_Mutation_LOCK"); -#endif - -slim_refcount_t *gSLiM_Mutation_Refcounts = nullptr; - -#define SLIM_MUTATION_BLOCK_INITIAL_SIZE 16384 // makes for about a 1 MB block; not unreasonable // NOLINT(*-macro-to-enum) : this is fine - -extern std::vector gEidosValue_Object_Mutation_Registry; // this is in Eidos; see SLiM_IncreaseMutationBlockCapacity() +#pragma mark - +#pragma mark Mutation +#pragma mark - -void SLiM_CreateMutationBlock(void) -{ - THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_CreateMutationBlock(): gSLiM_Mutation_Block address change"); - - // first allocate the block; no need to zero the memory - gSLiM_Mutation_Block_Capacity = SLIM_MUTATION_BLOCK_INITIAL_SIZE; - gSLiM_Mutation_Block = (Mutation *)malloc(gSLiM_Mutation_Block_Capacity * sizeof(Mutation)); - gSLiM_Mutation_Refcounts = (slim_refcount_t *)malloc(gSLiM_Mutation_Block_Capacity * sizeof(slim_refcount_t)); - - if (!gSLiM_Mutation_Block || !gSLiM_Mutation_Refcounts) - EIDOS_TERMINATION << "ERROR (SLiM_CreateMutationBlock): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - - //std::cout << "Allocating initial mutation block, " << SLIM_MUTATION_BLOCK_INITIAL_SIZE * sizeof(Mutation) << " bytes (sizeof(Mutation) == " << sizeof(Mutation) << ")" << std::endl; - - // now we need to set up our free list inside the block; initially all blocks are free - for (MutationIndex i = 0; i < gSLiM_Mutation_Block_Capacity - 1; ++i) - *(MutationIndex *)(gSLiM_Mutation_Block + i) = i + 1; - - *(MutationIndex *)(gSLiM_Mutation_Block + gSLiM_Mutation_Block_Capacity - 1) = -1; - - // now that the block is set up, we can start the free list - gSLiM_Mutation_FreeIndex = 0; -} +// A global counter used to assign all Mutation objects a unique ID +slim_mutationid_t gSLiM_next_mutation_id = 0; -void SLiM_IncreaseMutationBlockCapacity(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++) { - // We do not use a THREAD_SAFETY macro here because this needs to be checked in release builds also; - // we are not able to completely protect against this occurring at runtime, and it corrupts the run. - // It's OK for this to be called when we're inside an inactive parallel region; there is then no - // race condition. When a parallel region is active, even inside a critical region, reallocating - // the mutation block has the potential for a race with other threads. - if (omp_in_parallel()) - { - std::cerr << "ERROR (SLiM_IncreaseMutationBlockCapacity): (internal error) SLiM_IncreaseMutationBlockCapacity() was called to reallocate gSLiM_Mutation_Block inside a parallel section. If you see this message, you need to increase the pre-allocation margin for your simulation, because it is generating such an unexpectedly large number of new mutations. Please contact the SLiM developers for guidance on how to do this." << std::endl; - raise(SIGTRAP); - } - #ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.start_critical(1); + mutation_block_LOCK.start_critical(2); #endif - if (!gSLiM_Mutation_Block) - EIDOS_TERMINATION << "ERROR (SLiM_IncreaseMutationBlockCapacity): (internal error) called before SLiM_CreateMutationBlock()." << EidosTerminate(); - - // We need to expand the size of our Mutation block. This has the consequence of invalidating - // every Mutation * in the program. In general that is fine; we are careful to only keep - // pointers to Mutation temporarily, and for long-term reference we use MutationIndex. The - // exception to this is EidosValue_Object; the user can put references to mutations into - // variables that need to remain valid across reallocs like this. We therefore have to hunt - // down every EidosValue_Object that contains Mutations, and fix the pointer inside each of - // them. Because in SLiMgui all of the running simulations share a single Mutation block at - // the moment, in SLiMgui this patching has to occur across all of the simulations, not just - // the one that made this call. Yes, this is very gross. This is why pointers are evil. :-> - - // First let's do our realloc. We just need to note the change in value for the pointer. - // For now we will just double in size; we don't want to waste too much memory, but we - // don't want to have to realloc too often, either. - // BCH 11 May 2020: the realloc of gSLiM_Mutation_Block is technically problematic, because - // Mutation is non-trivially copyable according to C++. But it is safe, so I cast to void* - // in the hopes that that will make the warning go away. - std::uintptr_t old_mutation_block = reinterpret_cast(gSLiM_Mutation_Block); - MutationIndex old_block_capacity = gSLiM_Mutation_Block_Capacity; - - //std::cout << "old capacity: " << old_block_capacity << std::endl; - - // BCH 25 July 2023: check for increasing our block beyond the maximum size of 2^31 mutations. - // See https://github.com/MesserLab/SLiM/issues/361. Note that the initial size should be - // a power of 2, so that we actually reach the maximum; see SLIM_MUTATION_BLOCK_INITIAL_SIZE. - // In other words, we expect to be at exactly 0x0000000040000000UL here, and thus to double - // to 0x0000000080000000UL, which is a capacity of 2^31, which is the limit of int32_t. - if ((size_t)old_block_capacity > 0x0000000040000000UL) // >2^30 means >2^31 when doubled - EIDOS_TERMINATION << "ERROR (SLiM_IncreaseMutationBlockCapacity): too many mutations; there is a limit of 2^31 (2147483648) segregating mutations in SLiM." << EidosTerminate(nullptr); - - gSLiM_Mutation_Block_Capacity *= 2; - gSLiM_Mutation_Block = (Mutation *)realloc((void*)gSLiM_Mutation_Block, gSLiM_Mutation_Block_Capacity * sizeof(Mutation)); // NOLINT(*-realloc-usage) : realloc failure is a fatal error anyway - gSLiM_Mutation_Refcounts = (slim_refcount_t *)realloc(gSLiM_Mutation_Refcounts, gSLiM_Mutation_Block_Capacity * sizeof(slim_refcount_t)); // NOLINT(*-realloc-usage) : realloc failure is a fatal error anyway - - if (!gSLiM_Mutation_Block || !gSLiM_Mutation_Refcounts) - EIDOS_TERMINATION << "ERROR (SLiM_IncreaseMutationBlockCapacity): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + MutationBlock *mutation_block = species.SpeciesMutationBlock(); - //std::cout << "new capacity: " << gSLiM_Mutation_Block_Capacity << std::endl; + // initialize the tag to the "unset" value + tag_value_ = SLIM_TAG_UNSET_VALUE; - std::uintptr_t new_mutation_block = reinterpret_cast(gSLiM_Mutation_Block); + // 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; - // 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; + int trait_count = mutation_block->trait_count_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - *(MutationIndex *)(gSLiM_Mutation_Block + gSLiM_Mutation_Block_Capacity - 1) = gSLiM_Mutation_FreeIndex; + // 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. - gSLiM_Mutation_FreeIndex = old_block_capacity; + is_neutral_ = true; // will be set to false below as needed - // Now we go out and fix Mutation * references in EidosValue_Object in all symbol tables - if (new_mutation_block != old_mutation_block) + for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - // 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) + 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 + slim_effect_t effect = trait_index ? p_selection_coeff : 0.0; + slim_effect_t dominance = trait_index ? p_dominance_coeff : 0.5; + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably + + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; + + if (effect != 0.0) { - std::uintptr_t ptr_diff = old_mutation_block - new_mutation_block; + is_neutral_ = false; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation - for (EidosValue_Object *mutation_value : gEidosValue_Object_Mutation_Registry) - mutation_value->PatchPointersBySubtracting(ptr_diff); + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + } } - else + else // (effect == 0.0) { - std::uintptr_t ptr_diff = new_mutation_block - old_mutation_block; - - for (EidosValue_Object *mutation_value : gEidosValue_Object_Mutation_Registry) - mutation_value->PatchPointersByAdding(ptr_diff); + 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.end_critical(); +#if DEBUG_MUTATIONS + std::cout << "Mutation constructed: " << this << std::endl; #endif -} - -void SLiM_ZeroRefcountBlock(MutationRun &p_mutation_registry, bool p_registry_only) -{ - THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_ZeroRefcountBlock(): gSLiM_Mutation_Block change"); -#ifdef SLIMGUI - // BCH 11/25/2017: This code path needs to be used in SLiMgui to avoid modifying the refcounts - // for mutations in other simulations sharing the mutation block. - p_registry_only = true; +#ifdef DEBUG_LOCKS_ENABLED + mutation_block_LOCK.end_critical(); #endif - - if (p_registry_only) - { - // This code path zeros out refcounts just for the mutations currently in use in the registry. - // It is thus minimal, but probably quite a bit slower than just zeroing out the whole thing. - // BCH 6/8/2023: This is necessary in SLiMgui, as noted above, but also in multispecies sims - // so that one species does not step on the toes of another species. - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; - const MutationIndex *registry_iter = p_mutation_registry.begin_pointer_const(); - const MutationIndex *registry_iter_end = p_mutation_registry.end_pointer_const(); - - while (registry_iter != registry_iter_end) - *(refcount_block_ptr + (*registry_iter++)) = 0; - } - else - { - // Zero out the whole thing with EIDOS_BZERO(), without worrying about which bits are in use. - // This hits more memory, but avoids having to read the registry, and should write whole cache lines. - EIDOS_BZERO(gSLiM_Mutation_Refcounts, (gSLiM_Mutation_Block_LastUsedIndex + 1) * sizeof(slim_refcount_t)); - } -} - -size_t SLiMMemoryUsageForMutationBlock(void) -{ - return gSLiM_Mutation_Block_Capacity * sizeof(Mutation); } -size_t SLiMMemoryUsageForFreeMutations(void) -{ - size_t mut_count = 0; - MutationIndex nextFreeBlock = gSLiM_Mutation_FreeIndex; - - while (nextFreeBlock != -1) - { - mut_count++; - nextFreeBlock = *(MutationIndex *)(gSLiM_Mutation_Block + nextFreeBlock); - } - - return mut_count * sizeof(Mutation); -} - -size_t SLiMMemoryUsageForMutationRefcounts(void) -{ - return gSLiM_Mutation_Block_Capacity * sizeof(slim_refcount_t); -} - - -#pragma mark - -#pragma mark Mutation -#pragma mark - - -// 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 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 - gSLiM_Mutation_LOCK.start_critical(2); + mutation_block_LOCK.start_critical(2); #endif + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + // initialize the tag to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; - // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_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_); + // zero out our refcount and per-trait information, which is now kept in a separate buffer + MutationIndex mutation_index = mutation_block->IndexInBlock(this); + mutation_block->refcount_buffer_[mutation_index] = 0; + + int trait_count = mutation_block->trait_count_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - // zero out our refcount, which is now kept in a separate buffer - gSLiM_Mutation_Refcounts[BlockIndex()] = 0; + // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since + // this is critical path. See those methods for more comments on what is happening here. + + is_neutral_ = true; // will be set to false below as needed + + if (mutation_type_ptr_->all_pure_neutral_DES_) + { + // The DES of the mutation type is pure neutral, so we don't need to do any draws; we can short-circuit + // most of the work here and just set up neutral effects for all of the traits. + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = traits[trait_index]; + TraitType traitType = trait->Type(); + + traitInfoRec->effect_size_ = 0.0; + traitInfoRec->dominance_coeff_ = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); + traitInfoRec->hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } + } + } + else + { + // The DES of the mutation type is not pure neutral. Note that species.pure_neutral_ might still be true + // at this point; the mutation type for this mutation might not be used by any genomic element type, + // because we might be getting called by addNewDrawn() mutation for a type that is otherwise unused. + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = traits[trait_index]; + TraitType traitType = trait->Type(); + + slim_effect_t effect = mutation_type_ptr_->DrawEffectForTrait(trait_index); + slim_effect_t dominance = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); + + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; + + if (effect != 0.0) + { + is_neutral_ = false; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + } + } + else // (effect == 0.0) + { + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } + } + } + } #if DEBUG_MUTATIONS std::cout << "Mutation constructed: " << this << std::endl; #endif #ifdef DEBUG_LOCKS_ENABLED - 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; - } - } + mutation_block_LOCK.end_critical(); #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 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) { + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + // initialize the tag to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; - // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_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_); + // zero out our refcount and per-trait information, which is now kept in a separate buffer + MutationIndex mutation_index = mutation_block->IndexInBlock(this); + mutation_block->refcount_buffer_[mutation_index] = 0; + + int trait_count = mutation_block->trait_count_; + MutationTraitInfo *mut_trait_info = mutation_block->trait_info_buffer_ + trait_count * mutation_index; - // zero out our refcount, which is now kept in a separate buffer - gSLiM_Mutation_Refcounts[BlockIndex()] = 0; + // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since + // this is critical path. See those methods for more comments on what is happening here. + + is_neutral_ = true; // will be set to false by EffectChanged() as needed + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = species.Traits()[trait_index]; + TraitType traitType = trait->Type(); + + // FIXME MULTITRAIT: The per-trait info will soon supplant selection_coeff_ and dominance_coeff_; this initialization code needs to be fleshed out + slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : 0.0; + slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : 0.5; + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); + + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; + + if (effect != 0.0) + { + is_neutral_ = false; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + } + } + else // (effect == 0.0) + { + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } + } + } #if DEBUG_MUTATIONS std::cout << "Mutation constructed: " << this << std::endl; #endif // Since a mutation id was supplied by the caller, we need to ensure that subsequent mutation ids generated do not collide - // This constructor (unline the other Mutation() constructor above) is presently never called multithreaded, + // This constructor (unlike the other Mutation() constructor above) is presently never called multithreaded, // so we just enforce that here. If that changes, it should start using the debug lock to detect races, as above. THREAD_SAFETY_IN_ACTIVE_PARALLEL("Mutation::Mutation(): gSLiM_next_mutation_id change"); @@ -327,20 +337,136 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ gSLiM_next_mutation_id = mutation_id_ + 1; } +// This should be called whenever a mutation effect is changed; it handles the necessary recaching +void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect) +{ + slim_effect_t old_effect = traitInfoRec->effect_size_; + slim_effect_t dominance = traitInfoRec->dominance_coeff_; + slim_effect_t hemizygous_dominance = traitInfoRec->hemizygous_dominance_coeff_; + + traitInfoRec->effect_size_ = p_new_effect; + + if (p_new_effect != 0.0) + { + if (old_effect == 0.0) + { + // This mutation is no longer neutral; various observers care about that change + is_neutral_ = false; + + Species &species = mutation_type_ptr_->species_; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + } + + // cache values used by the fitness calculation code for speed; see header + + if (traitType == TraitType::kMultiplicative) + { + // For multiplicative traits, we clamp the lower end to 0.0; you can't be more lethal than lethal, and we + // never want to go negative and then go positive again by multiplying in another negative effect. There + // is admittedly a philosophical issue here; if a multiplicative trait represented simply some abstract + // trait with no direct connection to fitness, then maybe clamping here would not make sense? But even + // then, negative effects don't really seem to me to make sense there, so I think this is good. + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_effect); // 1 + s + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * p_new_effect); // 1 + hs + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * p_new_effect); // 1 + hs (using hemizygous h) + } + else // (traitType == TraitType::kAdditive) + { + // For additive traits, the baseline of the trait is arbitrary and there is no cutoff. + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * p_new_effect); // 2a + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * p_new_effect); // 2ha + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using hemizygous h) + } + } + else // p_new_effect == 0.0 + { + if (old_effect != 0.0) + { + // Changing from non-neutral to neutral; various observers care about that + // Note that we cannot set is_neutral_ and other such flags to true here, because only this trait's + // effect has changed to neutral; other trait effects might be non-neutral, which we don't check + Species &species = mutation_type_ptr_->species_; + + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + + // cache values used by the fitness calculation code for speed; see header + // for a neutral trait, we can set up this info very quickly + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } + } + } +} + +// This should be called whenever a mutation dominance is changed; it handles the necessary recaching +void Mutation::SetDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) +{ + traitInfoRec->dominance_coeff_ = p_new_dominance; + + // We only need to recache the heterozygous_effect_ values, since only they are affected by the change in + // dominance coefficient. Changing dominance has no effect on is_neutral_ or any of the other is-neutral + // flags. So this is very simple. + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_dominance * traitInfoRec->effect_size_); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); + } +} + +void Mutation::SetHemizygousDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) +{ + traitInfoRec->hemizygous_dominance_coeff_ = p_new_dominance; + + // We only need to recache the hemizygous_effect_ values, since only they are affected by the change in + // dominance coefficient. Changing dominance has no effect on is_neutral_ or any of the other is-neutral + // flags. So this is very simple. + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_dominance * traitInfoRec->effect_size_); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); + } +} + void Mutation::SelfDelete(void) { // This is called when our retain count reaches zero // 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 +486,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) @@ -389,8 +516,90 @@ 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(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(effect); + } + + return EidosValue_SP(float_result); + } + } + case gID_dominance: + { + // This is not accelerated, because it's a bit tricky; each mutation could belong to a different species, + // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + const std::vector &traits = species.Traits(); + size_t trait_count = traits.size(); + + if (trait_count == 1) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[0].dominance_coeff_)); + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; + + float_result->push_float_no_check(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(mut_trait_info[0].hemizygous_dominance_coeff_)); + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } + } // variables case gID_nucleotide: // ACCELERATED @@ -437,12 +646,47 @@ 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_; + 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) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].effect_size_)); + } + else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 19); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].hemizygous_dominance_coeff_)); + } + else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 9); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].dominance_coeff_)); + } + 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 +699,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 +714,9 @@ EidosValue *Mutation::GetProperty_Accelerated_isFixed(EidosObject **p_values, si return logical_result; } -EidosValue *Mutation::GetProperty_Accelerated_isSegregating(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_isSegregating(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -483,8 +729,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 +755,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 +774,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 +789,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 +804,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 +819,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 +838,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 +901,65 @@ 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); + + SetEffect(trait->Type(), traitInfoRec, new_effect); + return; + } + } + else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 19); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); + slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + + SetHemizygousDominance(trait->Type(), traitInfoRec, new_dominance); + return; + } + } + else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 9); + 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); + + SetDominance(trait->Type(), traitInfoRec, new_dominance); + 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 +976,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 +1000,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([Nio 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(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(effect); } + + return EidosValue_SP(float_result); } - else if (old_coeff != 0.0) // && (selection_coeff_ == 0.0) implied by the "else" +} + +// ********************* - (float)dominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); + + // get the trait info for this mutation + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + + if (trait_indices.size() == 1) { - Species &species = mutation_type_ptr_->species_; + int64_t trait_index = trait_indices[0]; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; + + float_result->push_float_no_check(dominance); + } - // 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_++; + return EidosValue_SP(float_result); } +} + +// ********************* - (float)hemizygousDominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); - // 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(dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } } // ********************* - (void)setMutationType(io$ mutType) @@ -775,13 +1139,16 @@ 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; + // FIXME MULTITRAIT: I think it might be useful for MutationType to keep a flag separately for each trait, whether *that* trait is all_pure_neutral_DES_ or not + //int trait_count = species.TraitCount(); + //MutationBlock *mutation_block = species.SpeciesMutationBlock(); + //MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + + if (!is_neutral_) + mutation_type_ptr_->all_pure_neutral_DES_ = false; - // 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; } @@ -816,7 +1183,9 @@ const std::vector *Mutation_Class::Properties(void) properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_nucleotideValue)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_originTick)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_position)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_selectionCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_selectionCoeff)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_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 +1205,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))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setMutationType, kEidosValueMaskVOID))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); @@ -845,6 +1219,420 @@ 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([Nio trait = NULL], [Nif effect = NULL]) +// +EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *effect_value = p_arguments[1].get(); + + int mutations_count = p_target->Count(); + int effect_count = effect_value->Count(); + + if (mutations_count == 0) + return gStaticEidosValueVOID; + + Mutation **mutations_buffer = (Mutation **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForMutations(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that all mutations belong to the same species." << EidosTerminate(); + + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + 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]->Type(), 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]->Type(), 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]->Type(), 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]->Type(), 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]->Type(), 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]->Type(), 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]->Type(), 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]->Type(), 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(); + + return gStaticEidosValueVOID; +} + +// ********************* + (void)setDominanceForTrait([Nio trait = NULL], [Nif dominance = NULL]) +// ********************* + (void)setHemizygousDominanceForTrait([Nio trait = NULL], [Nif dominance = NULL]) +// +EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_interpreter) + const char *method_name = (p_method_id == gID_setDominanceForTrait) ? "setDominanceForTrait" : "setHemizygousDominanceForTrait"; + + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *dominance_value = p_arguments[1].get(); + + 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]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), 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]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), 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]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), 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]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), 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]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), 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]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), 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]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), 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]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), 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(); + + return gStaticEidosValueVOID; +} + + + diff --git a/core/mutation.h b/core/mutation.h index 755685c3..26ef2c2c 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -41,7 +41,7 @@ extern EidosClass *gSLiM_Mutation_Class; // A global counter used to assign all Mutation objects a unique ID extern slim_mutationid_t gSLiM_next_mutation_id; -// A MutationIndex is an index into gSLiM_Mutation_Block (see below); it is used as, in effect, a Mutation *, but is 32-bit. +// A MutationIndex is an index into a MutationBlock (see mutation_block.h); it is used as, in effect, a Mutation *, but is 32-bit. // Note that type int32_t is used instead of uint32_t so that -1 can be used as a "null pointer"; perhaps UINT32_MAX would be // better, but on the other hand using int32_t has the virtue that if we run out of room we will probably crash hard rather // than perhaps just silently overrunning gSLiM_Mutation_Block with mysterious memory corruption bugs that are hard to catch. @@ -51,11 +51,26 @@ extern slim_mutationid_t gSLiM_next_mutation_id; // difficult to code since MutationRun's internal buffer of MutationIndex is accessible and used directly by many clients. typedef int32_t MutationIndex; -// forward declaration of Mutation block allocation; see bottom of header -class Mutation; -extern Mutation *gSLiM_Mutation_Block; -extern MutationIndex gSLiM_Mutation_Block_Capacity; - +// This structure contains all of the information about how a mutation influences a particular trait: in particular, its +// effect size and dominance coefficient. Each mutation keeps this information for each trait in its species, and since +// the number of traits is determined at runtime, the size of this data -- the number of MutationTraitInfo records kept +// by each mutation -- is also determined at runtime. We don't want to make a separate malloced block for each mutation; +// that would be far too expensive. Instead, MutationBlock keeps a block of MutationTraitInfo records for the species, +// with a number of records per mutation that is determined when it is constructed. +typedef struct _MutationTraitInfo +{ + slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) + slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t hemizygous_dominance_coeff_; // hemizygous dominance coefficient (h_hemi), inherited from MutationType by default + + // We cache values used in the fitness calculation code, for speed. These are the final fitness effects of this mutation + // when it is homozygous or heterozygous, respectively. These values are clamped to a minimum of 0.0, so that multiplying + // 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 +91,17 @@ 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 0 state is sticky, so if the mutation is ever marked non-neutral then it stays marked non-neutral, + // just because re-evaluating that requires scanning across the effects for all traits -- not worth it + // this is used to make constructing non-neutral caches for fitness evaluation fast with multiple traits + unsigned int is_neutral_ : 1; + int8_t nucleotide_; // the nucleotide being kept: A=0, C=1, G=2, T=3. -1 is used to indicate non-nucleotide-based. int8_t scratch_; // temporary scratch space for use by algorithms; regard as volatile outside your own code block const slim_mutationid_t mutation_id_; // a unique id for each mutation, used to track mutations @@ -91,15 +112,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 +119,22 @@ 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); + + // These should be called whenever a mutation effect/dominance is changed; they handle the necessary recaching + void SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect); + void SetDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); + void SetHemizygousDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline #if DEBUG_MUTATIONS @@ -122,8 +148,6 @@ class Mutation : public EidosDictionaryRetained virtual void SelfDelete(void) override; - inline __attribute__((always_inline)) MutationIndex BlockIndex(void) const { return (MutationIndex)(this - gSLiM_Mutation_Block); } - // // Eidos support // @@ -133,25 +157,26 @@ 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_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 +207,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..1fc437fc 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -19,6 +19,8 @@ #include "mutation_run.h" +#include "species.h" +#include "mutation_block.h" #include @@ -67,11 +69,12 @@ bool MutationRun::contains_mutation(const Mutation *p_mut) const // binary search bool MutationRun::contains_mutation(const Mutation *p_mut) const { - MutationIndex mutation_index = p_mut->BlockIndex(); + MutationBlock *mutation_block = p_mut->mutation_type_ptr_->mutation_block_; + MutationIndex mutation_index = mutation_block->IndexInBlock(p_mut); slim_position_t position = p_mut->position_; int mut_count = size(); const MutationIndex *mut_ptr = begin_pointer_const(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; int mut_index; { @@ -147,7 +150,7 @@ bool MutationRun::contains_mutation(const Mutation *p_mut) const Mutation *MutationRun::mutation_with_type_and_position(MutationType *p_mut_type, slim_position_t p_position, slim_position_t p_last_position) const { - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = p_mut_type->mutation_block_->mutation_buffer_; int mut_count = size(); const MutationIndex *mut_ptr = begin_pointer_const(); int mut_index; @@ -255,7 +258,7 @@ Mutation *MutationRun::mutation_with_type_and_position(MutationType *p_mut_type, return nullptr; } -const std::vector *MutationRun::derived_mutation_ids_at_position(slim_position_t p_position) const +const std::vector *MutationRun::derived_mutation_ids_at_position(Mutation *p_mut_block_ptr, slim_position_t p_position) const { THREAD_SAFETY_IN_ACTIVE_PARALLEL("MutationRun::derived_mutation_ids_at_position(): usage of statics"); @@ -269,11 +272,10 @@ const std::vector *MutationRun::derived_mutation_ids_at_position(sli // but fast for the other cases, such as new SLiM-generated mutations, which are much more common. const MutationIndex *begin_ptr = begin_pointer_const(); const MutationIndex *end_ptr = end_pointer_const(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; for (const MutationIndex *mut_ptr = end_ptr - 1; mut_ptr >= begin_ptr; --mut_ptr) { - Mutation *mut = mut_block_ptr + *mut_ptr; + Mutation *mut = p_mut_block_ptr + *mut_ptr; slim_position_t mut_position = mut->position_; if (mut_position == p_position) @@ -285,7 +287,7 @@ const std::vector *MutationRun::derived_mutation_ids_at_position(sli return &return_vec; } -void MutationRun::_RemoveFixedMutations(void) +void MutationRun::_RemoveFixedMutations(Mutation *p_mut_block_ptr) { // Mutations that have fixed, and are thus targeted for removal, have had their state_ set to kFixedAndSubstituted. // That is done only when convertToSubstitution == T, so we don't need to check that flag here. @@ -295,14 +297,13 @@ void MutationRun::_RemoveFixedMutations(void) MutationIndex *haplosome_iter = mutations_; MutationIndex *haplosome_backfill_iter = nullptr; MutationIndex *haplosome_max = mutations_ + mutation_count_; - Mutation *mutation_block_ptr = gSLiM_Mutation_Block; // haplosome_iter advances through the mutation list; for each entry it hits, the entry is either fixed (skip it) or not fixed // (copy it backward to the backfill pointer). We do this with two successive loops; the first knows that no mutation has // yet been skipped, whereas the second knows that at least one mutation has been. while (haplosome_iter != haplosome_max) { - if ((mutation_block_ptr + (*haplosome_iter++))->state_ != MutationState::kFixedAndSubstituted) + if ((p_mut_block_ptr + (*haplosome_iter++))->state_ != MutationState::kFixedAndSubstituted) continue; // Fixed mutation; we want to omit it, so we skip it in haplosome_backfill_iter and transition to the second loop @@ -320,7 +321,7 @@ void MutationRun::_RemoveFixedMutations(void) { MutationIndex mutation_index = *haplosome_iter; - if ((mutation_block_ptr + mutation_index)->state_ != MutationState::kFixedAndSubstituted) + if ((p_mut_block_ptr + mutation_index)->state_ != MutationState::kFixedAndSubstituted) { // Unfixed mutation; we want to keep it, so we copy it backward and advance our backfill pointer as well as haplosome_iter *haplosome_backfill_iter = mutation_index; @@ -347,11 +348,10 @@ void MutationRun::_RemoveFixedMutations(void) } } -bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, MutationStackPolicy p_policy, int64_t p_stack_group) +bool MutationRun::_EnforceStackPolicyForAddition(Mutation *p_mut_block_ptr, slim_position_t p_position, MutationStackPolicy p_policy, int64_t p_stack_group) { MutationIndex *begin_ptr = begin_pointer(); MutationIndex *end_ptr = end_pointer(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; if (p_policy == MutationStackPolicy::kKeepFirst) { @@ -359,7 +359,7 @@ bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, Mut // We scan in reverse order, because usually we're adding mutations on the end with emplace_back() for (MutationIndex *mut_ptr = end_ptr - 1; mut_ptr >= begin_ptr; --mut_ptr) { - Mutation *mut = mut_block_ptr + *mut_ptr; + Mutation *mut = p_mut_block_ptr + *mut_ptr; slim_position_t mut_position = mut->position_; if ((mut_position == p_position) && (mut->mutation_type_ptr_->stack_group_ == p_stack_group)) @@ -378,7 +378,7 @@ bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, Mut for (MutationIndex *mut_ptr = end_ptr - 1; mut_ptr >= begin_ptr; --mut_ptr) { - Mutation *mut = mut_block_ptr + *mut_ptr; + Mutation *mut = p_mut_block_ptr + *mut_ptr; slim_position_t mut_position = mut->position_; if ((mut_position == p_position) && (mut->mutation_type_ptr_->stack_group_ == p_stack_group)) @@ -396,7 +396,7 @@ bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, Mut for ( ; mut_ptr < end_ptr; ++mut_ptr) { MutationIndex mut_index = *mut_ptr; - Mutation *mut = mut_block_ptr + mut_index; + Mutation *mut = p_mut_block_ptr + mut_index; slim_position_t mut_position = mut->position_; if ((mut_position == p_position) && (mut->mutation_type_ptr_->stack_group_ == p_stack_group)) @@ -421,14 +421,14 @@ bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, Mut EIDOS_TERMINATION << "ERROR (MutationRun::_EnforceStackPolicyForAddition): (internal error) invalid policy." << EidosTerminate(); } -void MutationRun::split_run(MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const +void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const { MutationRun *first_half = NewMutationRun(p_mutrun_context); MutationRun *second_half = NewMutationRun(p_mutrun_context); int32_t second_half_start; for (second_half_start = 0; second_half_start < mutation_count_; ++second_half_start) - if ((gSLiM_Mutation_Block + mutations_[second_half_start])->position_ >= p_split_first_position) + if ((p_mut_block_ptr + mutations_[second_half_start])->position_ >= p_split_first_position) break; if (second_half_start > 0) @@ -444,7 +444,7 @@ void MutationRun::split_run(MutationRun **p_first_half, MutationRun **p_second_h #if SLIM_USE_NONNEUTRAL_CACHES -void MutationRun::cache_nonneutral_mutations_REGIME_1() const +void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const { // // Regime 1 means there are no mutationEffect() callbacks at all, so neutrality can be assessed @@ -452,20 +452,29 @@ void MutationRun::cache_nonneutral_mutations_REGIME_1() const // zero_out_nonneutral_buffer(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; + Mutation *mutptr = p_mut_block_ptr + mutindex; - if ((mut_block_ptr + mutindex)->selection_coeff_ != 0.0) + if (!mutptr->is_neutral_) add_to_nonneutral_buffer(mutindex); } } -void MutationRun::cache_nonneutral_mutations_REGIME_2() const +void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const { + // 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). @@ -476,23 +485,21 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2() const // zero_out_nonneutral_buffer(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; - Mutation *mutptr = mut_block_ptr + mutindex; + Mutation *mutptr = p_mut_block_ptr + mutindex; // The result of && is not order-dependent, but the first condition is checked first. // I expect many mutations would fail the first test (thus short-circuiting), whereas // few would fail the second test (i.e. actually be 0.0) in a QTL model. - if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && (mutptr->selection_coeff_ != 0.0)) + if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && !mutptr->is_neutral_) add_to_nonneutral_buffer(mutindex); } } -void MutationRun::cache_nonneutral_mutations_REGIME_3() const +void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const { // // Regime 3 means that there are mutationEffect() callbacks beyond the constant neutral global @@ -504,17 +511,15 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3() const // zero_out_nonneutral_buffer(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; - Mutation *mutptr = mut_block_ptr + mutindex; + Mutation *mutptr = p_mut_block_ptr + mutindex; // The result of || is not order-dependent, but the first condition is checked first. // I have reordered this to put the fast test first; or I'm guessing it's the fast test. - if ((mutptr->selection_coeff_ != 0.0) || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) + if (!mutptr->is_neutral_ || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) add_to_nonneutral_buffer(mutindex); } } @@ -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..f0e7ec9f 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,7 +694,7 @@ class MutationRun } // splitting mutation runs - void split_run(MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const; + void split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const; #if SLIM_USE_NONNEUTRAL_CACHES // caching non-neutral mutations; see above for comments about the "regime" etc. @@ -737,13 +737,13 @@ class MutationRun ++nonneutral_mutations_count_; } - void cache_nonneutral_mutations_REGIME_1() const; - void cache_nonneutral_mutations_REGIME_2() const; - void cache_nonneutral_mutations_REGIME_3() const; + void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const; + void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const; + void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const; void check_nonneutral_mutation_cache() const; - inline __attribute__((always_inline)) void beginend_nonneutral_pointers(const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const + inline __attribute__((always_inline)) void beginend_nonneutral_pointers(Mutation *p_mut_block_ptr, const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const { if ((nonneutral_change_validation_ != p_nonneutral_change_counter) || (nonneutral_mutations_count_ == -1)) { @@ -757,9 +757,9 @@ class MutationRun switch (p_nonneutral_regime) { - case 1: cache_nonneutral_mutations_REGIME_1(); break; - case 2: cache_nonneutral_mutations_REGIME_2(); break; - case 3: cache_nonneutral_mutations_REGIME_3(); break; + case 1: cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; + case 2: cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; + case 3: cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; } #if (SLIMPROFILING == 1) @@ -838,7 +838,7 @@ class MutationRun // We need MutationType below, but we can't include it at top because it requires MutationRun to be defined... #include "mutation_type.h" -inline __attribute__((always_inline)) bool MutationRun::enforce_stack_policy_for_addition(slim_position_t p_position, MutationType *p_mut_type_ptr) +inline __attribute__((always_inline)) bool MutationRun::enforce_stack_policy_for_addition(Mutation *p_mut_block_ptr, slim_position_t p_position, MutationType *p_mut_type_ptr) { MutationStackPolicy policy = p_mut_type_ptr->stack_policy_; @@ -850,7 +850,7 @@ inline __attribute__((always_inline)) bool MutationRun::enforce_stack_policy_for else { // Otherwise, a relatively complicated check is needed, so we call out to a non-inline function - return _EnforceStackPolicyForAddition(p_position, policy, p_mut_type_ptr->stack_group_); + return _EnforceStackPolicyForAddition(p_mut_block_ptr, p_position, policy, p_mut_type_ptr->stack_group_); } } diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 3537fec3..244e3167 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,27 @@ 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 + // cleared if any mutation of this type has its effect changed // note also that we do not set Species.pure_neutral_ here; we wait until this muttype is used - all_pure_neutral_DFE_ = ((dfe_type_ == DFEType::kFixed) && (dfe_parameters_[0] == 0.0)); + all_pure_neutral_DES_ = ((p_DES_type == DESType::kFixed) && (p_DES_parameters[0] == 0.0)); + + // set up DE entries for all traits; every trait is initialized identically, from the parameters given + EffectDistributionInfo DES_info; + + DES_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); + DES_info.default_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 +112,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 +124,159 @@ 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 +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 +284,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 +299,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 +354,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 +369,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 +384,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 +423,7 @@ std::ostream &operator<<(std::ostream &p_outstream, const MutationType &p_mutati p_outstream << ">}"; return p_outstream; -} +}*/ // @@ -439,49 +455,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 +467,6 @@ EidosValue_SP MutationType::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(color_sub_)); case gID_convertToSubstitution: return (convert_to_substitution_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); - case gID_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 +513,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 +528,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 +547,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 +576,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 +624,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 +643,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 +667,342 @@ 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)drawSelectionCoefficient([integer$ n = 1]) +// ********************* - (float$)defaultDominanceForTrait([Nio trait = NULL]) // -EidosValue_SP MutationType::ExecuteMethod_drawSelectionCoefficient(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(DefaultDominanceForTrait(trait_index))); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + float_result->push_float_no_check(DefaultDominanceForTrait(trait_index)); + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float$)defaultHemizygousDominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP MutationType::ExecuteMethod_defaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultHemizygousDominanceForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(DefaultHemizygousDominanceForTrait(trait_index))); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + float_result->push_float_no_check(DefaultHemizygousDominanceForTrait(trait_index)); + + return EidosValue_SP(float_result); + } +} + +// ********************* - (fs)effectDistributionParamsForTrait([Nio trait = NULL]) +// +EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#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([Nio 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([Nio trait = NULL], [integer$ n = 1]) +// +EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue_SP result_SP(nullptr); - EidosValue *n_value = p_arguments[0].get(); + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *n_value = p_arguments[1].get(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "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(DrawEffectForTrait(trait_index))); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size() * num_draws); + + // draw_index is the outer loop, so that we get num_draws sets of (one draw per trait) + for (int64_t draw_index = 0; draw_index < num_draws; ++draw_index) + for (int64_t trait_index : trait_indices) + float_result->push_float_no_check(DrawEffectForTrait(trait_index)); + + return EidosValue_SP(float_result); + } +} + +// ********************* - (void)setDefaultDominanceForTrait(Nio trait, float dominance) +// +EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *dominance_value = p_arguments[1].get(); + int dominance_count = dominance_value->Count(); - EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(num_draws); - result_SP = EidosValue_SP(float_result); + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultDominanceForTrait"); - for (int64_t draw_index = 0; draw_index < num_draws; ++draw_index) - float_result->set_float_no_check(DrawSelectionCoefficient(), draw_index); + 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; - return result_SP; + return gStaticEidosValueVOID; } -// ********************* - (void)setDistribution(string$ distributionType, ...) +// ********************* - (void)setDefaultHemizygousDominanceForTrait(Nio trait, float dominance) // -EidosValue_SP MutationType::ExecuteMethod_setDistribution(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +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 *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 *dominance_value = p_arguments[1].get(); + int dominance_count = dominance_value->Count(); - // Parse the DFE type and parameters, and do various sanity checks - DFEType dfe_type; - std::vector dfe_parameters; - std::vector dfe_strings; + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultHemizygousDominanceForTrait"); - MutationType::ParseDFEParameters(dfe_type_string, p_arguments.data() + 1, (int)p_arguments.size() - 1, &dfe_type, &dfe_parameters, &dfe_strings); + 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); - // 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; + // 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; - // 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; + return gStaticEidosValueVOID; +} + +// ********************* - (void)setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) +// +EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + 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 DES type and parameters, and do various sanity checks + DESType DES_type; + std::vector DES_parameters; + std::vector DES_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) DES; if so, we switch to a slower case when evolving + if (DES_type == DESType::kScript) + species_.type_s_DESs_present_ = true; + + // Everything seems to be in order, so replace our distribution info (in each specified trait) with the new info + for (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 we are now using a DES type that is non-neutral; check and set pure_neutral_ and all_pure_neutral_DES_ + if ((DES_type != DESType::kFixed) || (DES_parameters[0] != 0.0)) { species_.pure_neutral_ = false; - all_pure_neutral_DFE_ = false; + all_pure_neutral_DES_ = false; } return gStaticEidosValueVOID; @@ -823,10 +1031,6 @@ const std::vector *MutationType_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(MutationType::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_convertToSubstitution, false, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedSet(MutationType::SetProperty_Accelerated_convertToSubstitution)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_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 +1055,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))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultHemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } @@ -920,6 +1130,5 @@ const std::vector *MutationType_Class::Methods(void) c - diff --git a/core/mutation_type.h b/core/mutation_type.h index 3c02ce9c..20ea758d 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,14 @@ #include "slim_globals.h" class Species; +class MutationBlock; extern EidosClass *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 +58,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 +85,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 +114,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, @@ -121,17 +130,17 @@ class MutationType : public EidosDictionaryUnretained #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 + // 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. - // 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 + // all_pure_neutral_DES_ is true if the DES is "f" 0.0. It is cleared if any mutation of this type has its selection coefficient // changed, so it can be used as a reliable indicator that mutations of a given mutation type are actually neutral – except for // the effects of mutationEffect() callbacks, which might make them non-neutral in a given tick / subpopulation. - mutable bool all_pure_neutral_DFE_; + mutable bool all_pure_neutral_DES_; // 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_pure_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 +157,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 +172,32 @@ 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); + + 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 + bool IsPureNeutralDES(void) const { return all_pure_neutral_DES_; } // // Eidos support @@ -186,16 +211,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..90cc8703 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,39 @@ 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, 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 << ","; + + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].dominance_coeff_); // necessary precision for non-lossiness + p_out << double_buf; + } p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -73,20 +93,39 @@ 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, 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 << ","; + + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].dominance_coeff_); // necessary precision for non-lossiness + p_out << double_buf; + } p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -100,7 +139,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 +154,34 @@ void Polymorphism::Print_NoID_Tag(std::ostream &p_out) const p_out << " \"" << chromosome->Symbol() << "\""; } + p_out << " "; + + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + p_out << mut_trait_info[trait_index].effect_size_; + } + + p_out << " "; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + p_out << mut_trait_info[trait_index].dominance_coeff_; + } + // and then the remainder of the output line - p_out << " " << mutation_ptr_->selection_coeff_ << " " << mutation_ptr_->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 +201,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 +216,34 @@ void Polymorphism::Print_NoID(std::ostream &p_out) const p_out << " \"" << chromosome->Symbol() << "\""; } + p_out << " "; + + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + p_out << mut_trait_info[trait_index].effect_size_; + } + + p_out << " "; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + p_out << mut_trait_info[trait_index].dominance_coeff_; + } + // and then the remainder of the output line - p_out << " " << mutation_ptr_->selection_coeff_ << " " << mutation_ptr_->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 63814943..c969b97d 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 @@ -1197,7 +1202,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 +1729,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 +3003,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 +3166,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_pure_neutral_DES_ 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 +3190,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 +3260,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 +3278,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 +3410,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 +3428,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 +3464,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 +3482,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 +3619,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 +3637,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 +3808,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_pure_neutral_DES_ 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 +3841,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 +3890,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 +3908,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 +4052,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 +4210,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_pure_neutral_DES_ 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 +4234,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 +4349,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 +4367,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 +4403,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 +4421,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 +4558,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 +4576,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 +4905,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 +5012,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)); } } } @@ -5190,27 +5196,6 @@ 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) { // calculate the fitnesses of the parents and make lookup tables; the main thing we do here is manage the mutationEffect() callbacks @@ -5757,6 +5742,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 +5779,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 +5804,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 +6054,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 +6155,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 +6967,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 +7003,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 +7041,7 @@ void Population::TallyMutationReferencesAcrossSubpopulations(std::vector 1); + mutation_block->ZeroRefcountBlock(mutation_registry_); for (Chromosome *chromosome : species_.Chromosomes()) chromosome->tallied_haplosome_count_ = 0; @@ -7088,7 +7110,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 +7137,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 +7181,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 +7201,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) { @@ -7234,7 +7259,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) @@ -7261,8 +7288,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]; @@ -7282,7 +7307,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 @@ -7316,7 +7343,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; @@ -7329,7 +7356,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); @@ -7337,7 +7363,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_]; @@ -7351,7 +7377,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); @@ -7359,7 +7384,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); } } @@ -7369,7 +7394,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 @@ -7403,7 +7430,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_]; @@ -7416,7 +7443,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); @@ -7424,7 +7450,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); @@ -7476,8 +7502,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; @@ -7674,7 +7701,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); } } } @@ -7753,7 +7780,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 @@ -7773,7 +7800,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(); @@ -7819,7 +7846,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(); @@ -7902,7 +7929,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 @@ -7915,7 +7942,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 @@ -8091,7 +8118,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 { @@ -8131,14 +8158,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_; + // 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_; @@ -8293,8 +8325,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_; + 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_; @@ -8381,7 +8416,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 @@ -8435,7 +8470,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..581139ad 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,9 +220,6 @@ class Population Individual *(Subpopulation::*GenerateIndividualSelfed_TEMPLATED)(Individual *p_parent) = nullptr; Individual *(Subpopulation::*GenerateIndividualCloned_TEMPLATED)(Individual *p_parent) = nullptr; - // An internal method that validates cached fitness values kept by Mutation objects - void ValidateMutationFitnessCaches(void); - // Recalculate all fitness values for the parental generation, including the use of mutationEffect() callbacks void RecalculateFitness(slim_tick_t p_tick); @@ -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..c7d25c90 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -1807,6 +1807,9 @@ const std::vector *SLiMEidosBlock_Class::Properties( } +#ifdef EIDOS_GUI +// SLiMTypeTable and SLiMTypeInterpreter are only used in SLiMgui and QtSLiM + // // SLiMTypeTable // @@ -1996,6 +1999,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 +2091,7 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_MethodCall_Internal(const return ret; } +#endif // EIDOS_GUI diff --git a/core/slim_eidos_block.h b/core/slim_eidos_block.h index 6c9ffd39..69fd2eba 100644 --- a/core/slim_eidos_block.h +++ b/core/slim_eidos_block.h @@ -255,6 +255,8 @@ class SLiMEidosBlock_Class : public EidosClass virtual const std::vector *Properties(void) const override; }; +#ifdef EIDOS_GUI +// SLiMTypeTable and SLiMTypeInterpreter are only used in SLiMgui and QtSLiM #pragma mark - #pragma mark SLiMTypeTable @@ -311,6 +313,7 @@ class SLiMTypeInterpreter : public EidosTypeInterpreter virtual EidosTypeSpecifier _TypeEvaluate_MethodCall_Internal(const EidosClass *p_target, const EidosMethodSignature *p_method_signature, const std::vector &p_arguments) override; }; +#endif // EIDOS_GUI #endif /* defined(__SLiM__slim_script_block__) */ diff --git a/core/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 5d6efd73..d460fce6 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); @@ -1245,13 +1251,14 @@ const std::string &gStr_nucleotide = EidosRegisteredString("nucleotide", gID_nuc const std::string &gStr_nucleotideValue = EidosRegisteredString("nucleotideValue", gID_nucleotideValue); const std::string &gStr_originTick = EidosRegisteredString("originTick", gID_originTick); const std::string &gStr_position = EidosRegisteredString("position", gID_position); -const std::string &gStr_selectionCoeff = EidosRegisteredString("selectionCoeff", gID_selectionCoeff); const std::string &gStr_subpopID = EidosRegisteredString("subpopID", gID_subpopID); const std::string &gStr_convertToSubstitution = EidosRegisteredString("convertToSubstitution", gID_convertToSubstitution); -const std::string &gStr_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 +1272,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 +1360,10 @@ 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_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 +1381,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 +1401,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 +1581,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 +2628,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..947c7f26 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -124,7 +124,7 @@ typedef int64_t slim_mutationid_t; // identifiers for mutations, which require typedef int64_t slim_pedigreeid_t; // identifiers for pedigreed individuals; over many ticks in a large model maybe 64 bits? typedef int64_t slim_haplosomeid_t; // identifiers for pedigreed haplosomes; not user-visible, used by the tree-recording code, pedigree_id*2 + [0/1] typedef int32_t slim_polymorphismid_t; // identifiers for polymorphisms, which need only 32 bits since they are only segregating mutations -typedef float slim_selcoeff_t; // storage of selection coefficients in memory-tight classes; also dominance coefficients +typedef float slim_effect_t; // storage of trait effects (e.g., selection coefficients) in memory-tight classes; also dominance coefficients #define SLIM_MAX_TICK (1000000000L) // ticks range from 0 (init time) to this; SLIM_MAX_TICK + 1 is an "infinite" marker value #define SLIM_MAX_BASE_POSITION (1000000000000000L) // base positions in the chromosome can range from 0 to 1e15; see above @@ -451,8 +451,9 @@ typedef struct int64_t communityObjects_count; size_t communityObjects; - size_t mutationRefcountBuffer; // this pool is kept globally by Mutation - size_t mutationUnusedPoolSpace; // this pool is kept globally by Mutation + size_t mutationRefcountBuffer; // this pool is kept by Species + size_t mutationPerTraitBuffer; // this pool is kept by Species + size_t mutationUnusedPoolSpace; // this pool is kept by Species int64_t interactionTypeObjects_count; // InteractionType is kept by Community now size_t interactionTypeObjects; @@ -572,6 +573,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 +769,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 +782,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; @@ -826,13 +840,14 @@ 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 +861,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 +887,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 +948,10 @@ 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_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 +969,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 +989,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 +1165,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 +1254,7 @@ enum _SLiMGlobalStringID : int { gID_initializeGenomicElementType, gID_initializeMutationType, gID_initializeMutationTypeNuc, + gID_initializeTrait, gID_initializeChromosome, gID_initializeGeneConversion, gID_initializeMutationRate, @@ -1235,6 +1267,11 @@ enum _SLiMGlobalStringID : int { gID_initializeSLiMModelType, gID_initializeInteractionType, + gID_baselineOffset, + gID_individualOffsetMean, + gID_individualOffsetSD, + gID_directFitnessEffect, + gID_genomicElements, gID_lastPosition, gID_hotspotEndPositions, @@ -1287,13 +1324,14 @@ enum _SLiMGlobalStringID : int { 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 +1345,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 +1432,10 @@ enum _SLiMGlobalStringID : int { gID_positionsOfMutationsOfType, gID_containsMarkerMutation, gID_haplosomesForChromosomes, + gID_offsetForTrait, + gID_phenotypeForTrait, + gID_setOffsetForTrait, + gID_setPhenotypeForTrait, gID_relatedness, gID_sharedParentCount, gID_mutationsOfType, @@ -1409,10 +1453,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 +1472,8 @@ enum _SLiMGlobalStringID : int { gID_chromosomesOfType, gID_chromosomesWithIDs, gID_chromosomesWithSymbols, + gID_traitsWithIndices, + gID_traitsWithNames, gID_addSubpopSplit, gID_estimatedLastTick, gID_deregisterScriptBlock, @@ -1596,6 +1649,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..8264b4af 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,6 +430,7 @@ int RunSLiMTests(void) _RunChromosomeTests(); _RunMutationTests(); _RunHaplosomeTests(temp_path); + _RunMultitraitTests(); _RunSubpopulationTests(); _RunIndividualTests(); _RunSubstitutionTests(); diff --git a/core/slim_test.h b/core/slim_test.h index 248e787e..fb9901e8 100644 --- a/core/slim_test.h +++ b/core/slim_test.h @@ -49,6 +49,7 @@ extern void _RunGenomicElementTests(void); extern void _RunChromosomeTests(void); extern void _RunMutationTests(void); extern void _RunHaplosomeTests(const std::string &temp_path); +extern void _RunMultitraitTests(void); extern void _RunSubpopulationTests(void); extern void _RunIndividualTests(void); extern void _RunErrorPositionTests(void); diff --git a/core/slim_test_core.cpp b/core/slim_test_core.cpp index 1bbc800e..dedb9459 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) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index f9d2c63c..8d07b732 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,283 @@ 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() { T_height.directFitnessEffect = T; if (!identical(T_height.directFitnessEffect, T)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.directFitnessEffect = T; if (!identical(T_weight.directFitnessEffect, T)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.index, 0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.index, 1)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.individualOffsetMean, 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.individualOffsetMean, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; if (!identical(T_height.individualOffsetMean, 3.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; if (!identical(T_weight.individualOffsetMean, 2.5)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.individualOffsetSD, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.individualOffsetSD, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetSD = 3.5; if (!identical(T_height.individualOffsetSD, 3.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetSD = 2.5; if (!identical(T_weight.individualOffsetSD, 2.5)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.name, 'height')) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.name, 'weight')) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.species, sim)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.species, sim)) stop(); }"); + + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { if (!identical(T_height.tag, 12)) stop(); }", "before being set", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { if (!identical(T_weight.tag, 3)) stop(); }", "before being set", __LINE__); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.tag = 12; if (!identical(T_height.tag, 12)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.tag = 3; if (!identical(T_weight.tag, 3)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.type, 'multiplicative')) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.type, 'additive')) stop(); }"); + + // individual offset + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.offsetForTrait(T_height), rep(1.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.offsetForTrait(T_weight), rep(0.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.offsetForTrait(NULL), rep(c(1.0, 0.0), 5))) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, 3); p1.individuals.setOffsetForTrait(1, 4.5); if (!identical(p1.individuals.offsetForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, 1:5 * 2 - 1); p1.individuals.setOffsetForTrait(1, 1:5 * 2); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(NULL, 1:10); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(NULL, 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_height), rep(3.5, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_weight), rep(2.5, 5))) stop(); }"); + + // individual 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 (not yet fully implemented) + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.height, rep(0.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.weight, rep(0.0, 5))) stop(); }"); + 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(); }"); + } + + std::cout << "_RunMultitraitTests() done" << std::endl; +} + diff --git a/core/slim_test_other.cpp b/core/slim_test_other.cpp index 28f7eea0..31852bee 100644 --- a/core/slim_test_other.cpp +++ b/core/slim_test_other.cpp @@ -2556,13 +2556,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__); @@ -2718,7 +2718,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__); @@ -3001,7 +3001,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/species.cpp b/core/species.cpp index 46090798..cb778005 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 copies of it forget it; see CreateAndPromulgateMutationBlock + { + delete mutation_block_; + mutation_block_ = nullptr; + + for (auto muttype_iter : mutation_types_) + muttype_iter.second->mutation_block_ = nullptr; + + for (Chromosome *chromosome : chromosomes_) + chromosome->mutation_block_ = nullptr; + + population_.mutation_block_ = nullptr; + } } void Species::_MakeHaplosomeMetadataRecords(void) @@ -514,6 +534,127 @@ 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 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::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 +1308,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 +1347,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 +1378,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 +1389,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 +1402,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_ + // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_pure_neutral_DES_ if (selection_coeff != 0.0) { pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = false; + mutation_type_ptr->all_pure_neutral_DES_ = 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 +1660,8 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid int32_t double_size; double double_test; int64_t flags = 0; - int32_t slim_tick_t_size, slim_position_t_size, slim_objectid_t_size, slim_popsize_t_size, slim_refcount_t_size, slim_selcoeff_t_size, slim_mutationid_t_size, slim_polymorphismid_t_size, slim_age_t_size, slim_pedigreeid_t_size, slim_haplosomeid_t_size, slim_usertag_t_size; - int header_length = sizeof(double_size) + sizeof(double_test) + sizeof(flags) + sizeof(slim_tick_t_size) + sizeof(slim_position_t_size) + sizeof(slim_objectid_t_size) + sizeof(slim_popsize_t_size) + sizeof(slim_refcount_t_size) + sizeof(slim_selcoeff_t_size) + sizeof(slim_mutationid_t_size) + sizeof(slim_polymorphismid_t_size) + sizeof(slim_age_t_size) + sizeof(slim_pedigreeid_t_size) + sizeof(slim_haplosomeid_t_size) + sizeof(slim_usertag_t_size) + sizeof(file_tick) + sizeof(file_cycle) + sizeof(section_end_tag); + int32_t slim_tick_t_size, slim_position_t_size, slim_objectid_t_size, slim_popsize_t_size, slim_refcount_t_size, slim_effect_t_size, slim_mutationid_t_size, slim_polymorphismid_t_size, slim_age_t_size, slim_pedigreeid_t_size, slim_haplosomeid_t_size, slim_usertag_t_size; + int header_length = sizeof(double_size) + sizeof(double_test) + sizeof(flags) + sizeof(slim_tick_t_size) + sizeof(slim_position_t_size) + sizeof(slim_objectid_t_size) + sizeof(slim_popsize_t_size) + sizeof(slim_refcount_t_size) + sizeof(slim_effect_t_size) + sizeof(slim_mutationid_t_size) + sizeof(slim_polymorphismid_t_size) + sizeof(slim_age_t_size) + sizeof(slim_pedigreeid_t_size) + sizeof(slim_haplosomeid_t_size) + sizeof(slim_usertag_t_size) + sizeof(file_tick) + sizeof(file_cycle) + sizeof(section_end_tag); // this is how to add more header tags in future versions //if (file_version >= 9) @@ -1561,8 +1702,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 +1759,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 +2059,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 +2071,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 +2137,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 +2148,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 +2168,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_ + // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_pure_neutral_DES_ if (selection_coeff != 0.0) { pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = false; + mutation_type_ptr->all_pure_neutral_DES_ = false; } } @@ -2048,7 +2190,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 +2372,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 +2441,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 +2452,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) @@ -2448,12 +2589,14 @@ std::vector Species::CallbackBlocksMatching(slim_tick_t p_tick, void Species::RunInitializeCallbacks(void) { // zero out the initialization check counts + // FIXME: doing this here is error-prone; the species object should zero-initialize all of this stuff instead! num_species_inits_ = 0; num_slimoptions_inits_ = 0; num_mutation_type_inits_ = 0; num_ge_type_inits_ = 0; num_sex_inits_ = 0; num_treeseq_inits_ = 0; + num_trait_inits_ = 0; num_chromosome_inits_ = 0; num_mutrate_inits_ = 0; @@ -2463,6 +2606,7 @@ void Species::RunInitializeCallbacks(void) num_ancseq_inits_ = 0; num_hotmap_inits_ = 0; + has_implicit_trait_ = false; has_implicit_chromosome_ = false; // execute initialize() callbacks, which should always have a tick of 0 set @@ -2591,6 +2735,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 +2792,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->IsPureNeutralDES()) using_neutral_muttype = true; } } @@ -2674,6 +2822,29 @@ void Species::RunInitializeCallbacks(void) AllocateTreeSequenceTables(); } +void Species::CreateAndPromulgateMutationBlock(void) +{ + if (mutation_block_) + EIDOS_TERMINATION << "ERROR (Species::CreateAndPromulgateMutationBlock): (internal error) a mutation block has already been allocated." << EidosTerminate(); + + // first we make a new MutationBlock object for ourselves + mutation_block_ = new MutationBlock(*this, 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 @@ -2848,9 +3019,9 @@ void Species::WF_GenerateOffspring(void) 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 +3121,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_); } } @@ -7657,7 +7828,16 @@ void Species::MetadataForMutation(Mutation *p_mutation, MutationMetadataRec *p_m EIDOS_TERMINATION << "ERROR (Species::MetadataForMutation): (internal error) bad parameters to MetadataForMutation()." << EidosTerminate(); p_metadata->mutation_type_id_ = p_mutation->mutation_type_ptr_->mutation_type_id_; - p_metadata->selection_coeff_ = p_mutation->selection_coeff_; + + // FIXME MULTITRAIT: We need to figure out where we're going to multitrait information in .trees + // For now we just write out the effect for trait 0, but we need the dominance coeff too, and we need + // it for all traits in the model not just trait 0; this design is not going to work. See + // https://github.com/MesserLab/SLiM/issues/569 + MutationBlock *mutation_block = p_mutation->mutation_type_ptr_->mutation_block_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(p_mutation); + + p_metadata->selection_coeff_ = mut_trait_info[0].effect_size_; + p_metadata->subpop_index_ = p_mutation->subpop_index_; p_metadata->origin_tick_ = p_mutation->origin_tick_; p_metadata->nucleotide_ = p_mutation->nucleotide_; @@ -7671,7 +7851,13 @@ void Species::MetadataForSubstitution(Substitution *p_substitution, MutationMeta EIDOS_TERMINATION << "ERROR (Species::MetadataForSubstitution): (internal error) bad parameters to MetadataForSubstitution()." << EidosTerminate(); p_metadata->mutation_type_id_ = p_substitution->mutation_type_ptr_->mutation_type_id_; - p_metadata->selection_coeff_ = p_substitution->selection_coeff_; + + // FIXME MULTITRAIT: We need to figure out where we're going to multitrait information in .trees + // For now we just write out the effect for trait 0, but we need the dominance coeff too, and we need + // it for all traits in the model not just trait 0; this design is not going to work. See + // https://github.com/MesserLab/SLiM/issues/569 + p_metadata->selection_coeff_ = p_substitution->trait_info_[0].effect_size_; + p_metadata->subpop_index_ = p_substitution->subpop_index_; p_metadata->origin_tick_ = p_substitution->origin_tick_; p_metadata->nucleotide_ = p_substitution->nucleotide_; @@ -9676,6 +9862,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 +9898,8 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapconvert_to_substitution_)) { // this mutation is fixed, and the muttype wants substitutions, so make a substitution - Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); + // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec; likewise hemizygous dominance + Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->DefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); // FIXME MULTITRAIT population_.treeseq_substitutions_map_.emplace(position, sub); population_.substitutions_.emplace_back(sub); @@ -9721,9 +9910,10 @@ 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 + 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 +9925,11 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapall_pure_neutral_DFE_ = false; + mutation_type_ptr->all_pure_neutral_DES_ = false; } } } diff --git a/core/species.h b/core/species.h index 2b2b08aa..004f9109 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" @@ -54,6 +55,7 @@ extern "C" { class Community; class EidosInterpreter; class Individual; +class MutationBlock; class MutationType; class GenomicElementType; class InteractionType; @@ -78,6 +80,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 +97,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! 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 +182,11 @@ class Species : public EidosDictionaryUnretained bool has_genetics_ = true; // false if the species has no mutation, no recombination, no muttypes/getypes, no genomic elements + // We keep a MutationBlock object that stores all of the Mutation objects that belong to this species. + // Our mutations get allocated and freed using this block, and we use MutationIndex to reference them. + // This remains nullptr in no-genetics species, and is allocated only after initialize() is done. + MutationBlock *mutation_block_ = nullptr; // OWNED; contains all of our mutations + // for multiple chromosomes, we now have a vector of pointers to Chromosome objects, // as well as hash tables for quick lookup by id and symbol #if EIDOS_ROBIN_HOOD_HASHING @@ -202,6 +213,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 +236,9 @@ class Species : public EidosDictionaryUnretained // private initialization methods #if EIDOS_ROBIN_HOOD_HASHING typedef robin_hood::unordered_flat_map SUBPOP_REMAP_HASH; - #elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING typedef std::unordered_map SUBPOP_REMAP_HASH; - #endif +#endif SLiMFileFormat FormatOfPopulationFile(const std::string &p_file_string); // determine the format of a file/folder at the given path using leading bytes, etc. void _CleanAllReferencesToSpecies(EidosInterpreter *p_interpreter); // clean up in anticipation of loading new species state @@ -272,6 +298,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 +328,17 @@ class Species : public EidosDictionaryUnretained bool tables_initialized_ = false; // not checked everywhere, just when allocing and freeing, to avoid crashes - std::vector remembered_nodes_; // used to be called remembered_genomes_, but it remembers tskit nodes, which might + std::vector remembered_nodes_; // used to be called remembered_genomes_, but it remembers tskit nodes, which might // actually be shared by multiple haplosomes in different chromosomes //Individual *current_new_individual_; #if EIDOS_ROBIN_HOOD_HASHING typedef robin_hood::unordered_flat_map INDIVIDUALS_HASH; - #elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING typedef std::unordered_map INDIVIDUALS_HASH; - #endif +#endif INDIVIDUALS_HASH tabled_individuals_hash_; // look up individuals table row numbers from pedigree IDs - + bool running_coalescence_checks_ = false; // true if we check for coalescence after each simplification bool running_treeseq_crosschecks_ = false; // true if crosschecks between our tree sequence tables and SLiM's data are enabled int treeseq_crosschecks_interval_ = 1; // crosschecks, if enabled, will be done every treeseq_crosschecks_interval_ cycles @@ -359,15 +387,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 +403,6 @@ class Species : public EidosDictionaryUnretained int32_t nonneutral_change_counter_ = 0; int32_t last_nonneutral_regime_ = 0; // see mutation_run.h; 1 = no mutationEffect() callbacks, 2 = only constant-effect neutral callbacks, 3 = arbitrary callbacks - // this flag is set if the dominance coeff (regular or haploid) changes on any mutation type, as a signal that recaching needs to occur in Subpopulation::UpdateFitness() - bool any_dominance_coeff_changed_ = false; - // state about what symbols/names/identifiers have been used or are being used // used_subpop_ids_ has every subpop id ever used, even if no longer in use, with the *last* name used for that subpop // used_subpop_names_ has every name ever used EXCEPT standard p1, p2... names, even if the name got replaced by a new name later @@ -418,6 +443,27 @@ 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 @@ -425,6 +471,7 @@ class Species : public EidosDictionaryUnretained // Running cycles std::vector CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, int64_t p_chromosome_id); void RunInitializeCallbacks(void); + void CreateAndPromulgateMutationBlock(void); void EndCurrentChromosome(bool starting_new_chromosome); bool HasDoneAnyInitialization(void); void PrepareForCycle(void); @@ -481,6 +528,7 @@ class Species : public EidosDictionaryUnretained inline __attribute__((always_inline)) slim_tick_t TickPhase(void) { return tick_phase_; } inline __attribute__((always_inline)) bool HasGenetics(void) { return has_genetics_; } + inline __attribute__((always_inline)) MutationBlock *SpeciesMutationBlock(void) { return mutation_block_; } // FIXME MULTICHROM: We could cache a ref to this in some key spots like Chromosome, MutationType, and Subpopulation inline __attribute__((always_inline)) 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 +655,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 +686,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..62edacf9 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->IsPureNeutralDES()) pure_neutral_ = false; - // the mutation type's all_pure_neutral_DFE_ flag is presumably already set - } } EidosValueType mm_type = mutationMatrix_value->Type(); @@ -591,6 +590,11 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_function_name, p_arguments, p_interpreter) + // We allocate space that depends on the number of traits; so either traits must be explicitly defined + // already, or we implicitly define a single trait (the default behavior mirroring older SLiM versions) + if ((num_trait_inits_ == 0) && !has_implicit_trait_) + MakeImplicitTrait(); + // Figure out whether the mutation type is nucleotide-based bool nucleotide_based = (p_function_name == "initializeMutationTypeNuc"); @@ -604,31 +608,31 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: slim_objectid_t map_identifier = SLiM_ExtractObjectIDFromEidosValue_is(id_value, 0, 'm'); double dominance_coeff = dominanceCoeff_value->NumericAtIndex_NOCAST(0, nullptr); - std::string dfe_type_string = distributionType_value->StringAtIndex_NOCAST(0, nullptr); + std::string DES_type_string = 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); + 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 +651,17 @@ 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 << ", \"" << DES_type << "\""; - if (dfe_parameters.size() > 0) + if (DES_parameters.size() > 0) { - for (double dfe_param : dfe_parameters) - output_stream << ", " << dfe_param; + for (double DES_param : DES_parameters) + output_stream << ", " << DES_param; } else { - for (const std::string &dfe_param : dfe_strings) - output_stream << ", \"" << dfe_param << "\""; + for (const std::string &DES_param : DES_strings) + output_stream << ", \"" << DES_param << "\""; } output_stream << ");" << std::endl; @@ -1582,6 +1586,315 @@ 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 + double baselineOffset; + + if (baselineOffset_value->Type() == EidosValueType::kValueNULL) + baselineOffset = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; + else + baselineOffset = baselineOffset_value->FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(baselineOffset)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires baselineOffset to be a finite value (not NAN or INF)." << EidosTerminate(); + + // check that the default distribution is used or not used, in its entirety + if (individualOffsetMean_value->Type() != individualOffsetSD_value->Type()) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that both individual offset parameters be NULL (to use the default distribution) or be non-NULL (to specify a distribution)." << EidosTerminate(); + + // individualOffsetMean + double individualOffsetMean; + + if (individualOffsetMean_value->Type() == EidosValueType::kValueNULL) + individualOffsetMean = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; + else + individualOffsetMean = individualOffsetMean_value->FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(individualOffsetMean)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires individualOffsetMean to be a finite value (not NAN or INF)." << EidosTerminate(); + + // individualOffsetSD + double individualOffsetSD; + + if (individualOffsetSD_value->Type() == EidosValueType::kValueNULL) + individualOffsetSD = 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 != 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 +2098,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 +2186,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 +2287,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 +2355,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 +2960,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 +2987,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 +3385,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 +3481,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 +3770,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) { @@ -3940,7 +4330,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; @@ -4293,6 +4683,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); } @@ -4342,6 +4733,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..3f98829b 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(); @@ -1378,15 +1379,6 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect { const std::map &mut_types = species_.MutationTypes(); - // The FitnessOfParent...() methods called by this method rely upon cached fitness values - // kept inside the Mutation objects. Those caches may need to be validated before we can - // calculate fitness values. We check for that condition and repair it first. - if (species_.any_dominance_coeff_changed_) - { - population_.ValidateMutationFitnessCaches(); // note one subpop triggers it, but the recaching occurs for the whole sim - species_.any_dominance_coeff_changed_ = false; - } - // This function calculates the population mean fitness as a side effect double totalFitness = 0.0; @@ -1420,8 +1412,8 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect } // 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 + // We can do this if (a) all mutation types either use a neutral DES, or have been made neutral with a "return 1.0;" + // mutationEffect() callback that is active, (b) for the mutation types that use a neutral DES, no mutation has had its // selection coefficient changed, and (c) no mutationEffect() callbacks are active apart from "return 1.0;" type callbacks. // This is often the case for QTL-based models (such as Misha's coral model), and should produce a big speed gain, // so we do a pre-check here for this case. Note that we can ignore fitnessEffect() callbacks in this situation, @@ -1472,9 +1464,9 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect // by mutationEffect() callbacks. Note this block is the only place where is_pure_neutral_now_ is valid or used!!! if (skip_chromosomal_fitness) { - // first set a flag on all mut types indicating whether they are pure neutral according to their DFE + // first set a flag on all mut types indicating whether they are pure neutral according to their DES for (auto &mut_type_iter : mut_types) - mut_type_iter.second->is_pure_neutral_now_ = mut_type_iter.second->all_pure_neutral_DFE_; + mut_type_iter.second->is_pure_neutral_now_ = mut_type_iter.second->all_pure_neutral_DES_; // 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) @@ -2455,7 +2447,8 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int SLIM_PROFILE_BLOCK_START(); #endif - slim_objectid_t mutation_type_id = (gSLiM_Mutation_Block + p_mutation)->mutation_type_ptr_->mutation_type_id_; + Mutation *mut_block_ptr = species_.SpeciesMutationBlock()->mutation_buffer_; + slim_objectid_t mutation_type_id = (mut_block_ptr + p_mutation)->mutation_type_ptr_->mutation_type_id_; for (SLiMEidosBlock *mutationEffect_callback : p_mutationEffect_callbacks) { @@ -2538,7 +2531,7 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int else { // local variables for the callback parameters that we might need to allocate here, and thus need to free below - EidosValue_Object local_mut(gSLiM_Mutation_Block + p_mutation, gSLiM_Mutation_Class); + EidosValue_Object local_mut(mut_block_ptr + p_mutation, gSLiM_Mutation_Class); EidosValue_Float local_effect(p_computed_fitness); // We need to actually execute the script; we start a block here to manage the lifetime of the symbol table @@ -2959,7 +2952,8 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom single_callback_mut_type = species_.MutationTypeWithID(mutation_type_id); } - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + MutationBlock *mutation_block = species_.SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; if (haplosome1_null && haplosome2_null) { @@ -2981,7 +2975,7 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome_iter, *haplosome_max; - mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); + mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); @@ -2994,17 +2988,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { MutationIndex haplosome_mutindex = *haplosome_iter++; Mutation *mutation = mut_block_ptr + haplosome_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_hemizygousdom_sel = mutation_block->TraitInfoForIndex(haplosome_mutindex)[0].hemizygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome_mutindex, -1, mutation->cached_one_plus_hemizygousdom_sel_, p_mutationEffect_callbacks, haplosome->individual_); + w *= ApplyMutationEffectCallbacks(haplosome_mutindex, -1, cached_one_plus_hemizygousdom_sel, p_mutationEffect_callbacks, haplosome->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_hemizygousdom_sel_; + w *= cached_one_plus_hemizygousdom_sel; } } } @@ -3025,8 +3021,8 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; - mutrun1->beginend_nonneutral_pointers(&haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); - mutrun2->beginend_nonneutral_pointers(&haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); + mutrun1->beginend_nonneutral_pointers(mut_block_ptr, &haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); + mutrun2->beginend_nonneutral_pointers(mut_block_ptr, &haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); @@ -3048,17 +3044,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { // Process a mutation in haplosome1 since it is leading Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } if (++haplosome1_iter == haplosome1_max) @@ -3072,17 +3070,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { // Process a mutation in haplosome2 since it is leading Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } if (++haplosome2_iter == haplosome2_max) @@ -3110,17 +3110,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { // a match was found, so we multiply our fitness by the full selection coefficient Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].homozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, true, mutation->cached_one_plus_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, true, cached_one_plus_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_sel_; + w *= cached_one_plus_sel; } goto homozygousExit1; } @@ -3131,17 +3133,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient { Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } } @@ -3175,17 +3179,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient { Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } } @@ -3216,17 +3222,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { MutationIndex haplosome1_mutindex = *haplosome1_iter++; Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } } @@ -3235,17 +3243,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { MutationIndex haplosome2_mutindex = *haplosome2_iter++; Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } } } @@ -3286,7 +3296,8 @@ double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vect single_callback_mut_type = species_.MutationTypeWithID(mutation_type_id); } - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + MutationBlock *mutation_block = species_.SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome->mutrun_count_; double w = 1.0; @@ -3298,7 +3309,7 @@ double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vect // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome_iter, *haplosome_max; - mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); + mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); @@ -3310,17 +3321,19 @@ double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vect { MutationIndex haplosome_mutation = *haplosome_iter++; Mutation *mutation = (mut_block_ptr + haplosome_mutation); + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_sel = mutation_block->TraitInfoForIndex(haplosome_mutation)[0].homozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome_mutation, -1, mutation->cached_one_plus_sel_, p_mutationEffect_callbacks, haplosome->individual_); + w *= ApplyMutationEffectCallbacks(haplosome_mutation, -1, cached_one_plus_sel, p_mutationEffect_callbacks, haplosome->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_sel_; + w *= cached_one_plus_sel; } } } @@ -4502,6 +4515,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 +4904,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 +4991,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 +5075,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 +5278,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 +5555,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 +5642,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; @@ -6451,8 +6492,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 +6507,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 +6522,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 +6537,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,8 +6556,9 @@ EidosValue *Subpopulation::GetProperty_Accelerated_tag(EidosObject **p_values, s return int_result; } -EidosValue *Subpopulation::GetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size) +EidosValue *Subpopulation::GetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -6567,8 +6613,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,8 +6633,9 @@ void Subpopulation::SetProperty_Accelerated_tag(EidosObject **p_values, size_t p } } -void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { double source_value = p_source.FloatAtIndex_NOCAST(0, nullptr); @@ -8927,22 +8975,34 @@ EidosValue_SP Subpopulation::ExecuteMethod_setMigrationRates(EidosGlobalStringID int source_subpops_count = sourceSubpops_value->Count(); int rates_count = rates_value->Count(); std::vector subpops_seen; + bool saw_nonzero_rate = false, saw_self_reference = false; - if (source_subpops_count != rates_count) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() requires sourceSubpops and rates to be equal in size." << EidosTerminate(); + if ((source_subpops_count != rates_count) && (rates_count != 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() requires sourceSubpops and rates to be equal in size, or rates to be singleton." << EidosTerminate(); for (int value_index = 0; value_index < source_subpops_count; ++value_index) { EidosObject *source_subpop = SLiM_ExtractSubpopulationFromEidosValue_io(sourceSubpops_value, value_index, &species_.community_, &species_, "setMigrationRates()"); // SPECIES CONSISTENCY CHECK slim_objectid_t source_subpop_id = ((Subpopulation *)(source_subpop))->subpopulation_id_; - + double migrant_fraction = ((rates_count == 1) ? rates_value->NumericAtIndex_NOCAST(0, nullptr) : rates_value->NumericAtIndex_NOCAST(value_index, nullptr)); + + // BCH 11/16/2025: We used to require that the target subpop was not a member of sourceSubpops; we would + // raise an error in all cases if that occurred. Now we relax those rules slightly, to make it easier + // to zero out all immigration into a subpop or subpops; we allow self-reference, but *only* if *all* + // rates specified in the call are 0.0. So you can do, e.g., allSubpops.setMigrationRates(allSubpops, 0). + // See https://github.com/MesserLab/SLiM/issues/570. As part of that fix, we also now allow rates to + // provide a singleton value, used for all sourceSubpops. + if (migrant_fraction != 0.0) + saw_nonzero_rate = true; if (source_subpop_id == subpopulation_id_) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() does not allow migration to be self-referential (originating within the destination subpopulation)." << EidosTerminate(); + saw_self_reference = true; + if (saw_self_reference && saw_nonzero_rate) + EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() does not allow migration to be self-referential (originating within the destination subpopulation), except when all rates are zero (for convenience)." << EidosTerminate(); + + // can't specify the same source subpopulation twice if (std::find(subpops_seen.begin(), subpops_seen.end(), source_subpop_id) != subpops_seen.end()) EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() two rates set for subpopulation p" << source_subpop_id << "." << EidosTerminate(); - double migrant_fraction = rates_value->NumericAtIndex_NOCAST(value_index, nullptr); - population_.SetMigration(*this, source_subpop_id, migrant_fraction); subpops_seen.emplace_back(source_subpop_id); } diff --git a/core/subpopulation.h b/core/subpopulation.h index 0b4ace59..acbb6677 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -292,6 +292,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; } @@ -491,14 +496,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..dd4885be 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -23,6 +23,7 @@ #include "eidos_call_signature.h" #include "eidos_property_signature.h" #include "species.h" +#include "mutation_block.h" #include #include @@ -35,16 +36,48 @@ #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_), 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_ = mut_trait_info[trait_index].dominance_coeff_; + trait_info_[trait_index].hemizygous_dominance_coeff_ = mut_trait_info[trait_index].hemizygous_dominance_coeff_; + } } -Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, double p_selection_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide) : -mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), fixation_tick_(p_fixation_tick), chromosome_index_(p_chromosome_index), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id), tag_value_(SLIM_TAG_UNSET_VALUE) +Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_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)); + + trait_info_[0].effect_size_ = p_selection_coeff; + trait_info_[0].dominance_coeff_ = p_dominance_coeff; + trait_info_[0].hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(0); // FIXME MULTITRAIT: needs to be passed in + + for (int trait_index = 1; trait_index < trait_count; trait_index++) + { + trait_info_[trait_index].effect_size_ = 0.0; + trait_info_[trait_index].dominance_coeff_ = 0.0; + trait_info_[trait_index].hemizygous_dominance_coeff_ = 1.0; // FIXME MULTITRAIT: needs to be passed in + } } void Substitution::PrintForSLiMOutput(std::ostream &p_out) const @@ -63,8 +96,15 @@ void Substitution::PrintForSLiMOutput(std::ostream &p_out) const p_out << " \"" << chromosome->Symbol() << "\""; } + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; // 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 +131,15 @@ void Substitution::PrintForSLiMOutput_Tag(std::ostream &p_out) const p_out << " \"" << chromosome->Symbol() << "\""; } + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; // 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 +169,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) @@ -145,11 +193,87 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) return mutation_type_ptr_->SymbolTableEntry().second; case gID_position: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(position_)); - case gID_selectionCoeff: // ACCELERATED - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); + case gID_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(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(effect); + } + + return EidosValue_SP(float_result); + } + } + case gID_dominance: + { + // This is not accelerated, because it's a bit tricky; each substitution could belong to a different species, + // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + size_t trait_count = traits.size(); + + if (trait_count == 1) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[0].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].dominance_coeff_; + + float_result->push_float_no_check(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(trait_info_[0].hemizygous_dominance_coeff_)); + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } + } case gID_originTick: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(origin_tick_)); - case gID_fixationTick: // ACCELERATED + case gID_fixationTick: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(fixation_tick_)); // variables @@ -197,12 +321,45 @@ 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(trait_info_[trait->Index()].effect_size_)); + } + else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 19); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].hemizygous_dominance_coeff_)); + } + else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 9); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].dominance_coeff_)); + } + return super::GetProperty(p_property_id); } } -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 +372,9 @@ EidosValue *Substitution::GetProperty_Accelerated_id(EidosObject **p_values, siz return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_nucleotide(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_String *string_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_String())->Reserve((int)p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -240,8 +398,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 +417,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 +432,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 +447,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 +462,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 +477,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 +496,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 +516,6 @@ void Substitution::SetProperty(EidosGlobalStringID p_property_id, const EidosVal // All of our strings are in the global registry, so we can require a successful lookup switch (p_property_id) { - case gID_nucleotide: - { - const std::string &nucleotide = ((EidosValue_String &)p_value).StringRefAtIndex_NOCAST(0, nullptr); - - if (nucleotide_ == -1) - EIDOS_TERMINATION << "ERROR (Substitution::SetProperty): property nucleotide is only defined for nucleotide-based substitutions." << EidosTerminate(); - - if (nucleotide == gStr_A) nucleotide_ = 0; - else if (nucleotide == gStr_C) nucleotide_ = 1; - else if (nucleotide == gStr_G) nucleotide_ = 2; - else if (nucleotide == gStr_T) nucleotide_ = 3; - else EIDOS_TERMINATION << "ERROR (Substitution::SetProperty): property nucleotide may only be set to 'A', 'C', 'G', or 'T'." << EidosTerminate(); - return; - } - case gID_nucleotideValue: - { - int64_t nucleotide = p_value.IntAtIndex_NOCAST(0, nullptr); - - if (nucleotide_ == -1) - EIDOS_TERMINATION << "ERROR (Substitution::SetProperty): property nucleotideValue is only defined for nucleotide-based substitutions." << EidosTerminate(); - if ((nucleotide < 0) || (nucleotide > 3)) - EIDOS_TERMINATION << "ERROR (Substitution::SetProperty): property nucleotideValue may only be set to 0 (A), 1 (C), 2 (G), or 3 (T)." << EidosTerminate(); - - nucleotide_ = (int8_t)nucleotide; - return; - } case gID_subpopID: { slim_objectid_t value = SLiMCastToObjectidTypeOrRaise(p_value.IntAtIndex_NOCAST(0, nullptr)); @@ -417,7 +542,112 @@ 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([Nio trait = NULL]) +// +EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t effect = trait_info_[trait_index].effect_size_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(effect)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t effect = trait_info_[trait_index].effect_size_; + + float_result->push_float_no_check(effect); + } + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float)dominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float)hemizygousDominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "hemizygousDominanceForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); } } @@ -446,12 +676,14 @@ const std::vector *Substitution_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_mutationType)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_position)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_selectionCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_selectionCoeff)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominance,true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_subpopID)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, false, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotide)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotideValue)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_originTick)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_fixationTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_fixationTick)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_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 +702,10 @@ const std::vector *Substitution_Class::Methods(void) c methods = new std::vector(*super::Methods()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/substitution.h b/core/substitution.h index b8dad5b0..8c57fbed 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -37,6 +37,19 @@ extern EidosClass *gSLiM_Substitution_Class; +// This structure contains all of the information about how a substitution influenced a particular trait: in particular, its +// effect size and dominance coefficient. Each substitution keeps this information for each trait in its species, and since +// the number of traits is determined at runtime, the size of this data -- the number of SubstitutionTraitInfo records kept +// by each substitution -- is also determined at runtime. This is parallel to the MutationTraitInfo struct for mutations, +// but keeps less information since it is not used during fitness evaluation. Also unlike Mutation, which keeps all this +// in a block maintained by MutationBlock, we simply make a malloced block for each substitution; substitution is relatively +// rare and substitutions don't go away once created, so there is no need to overcomplicate this design. +typedef struct _SubstitutionTraitInfo +{ + slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) + slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t hemizygous_dominance_coeff_; // hemizygous dominance coefficient (h_hemi), inherited from MutationType by default +} SubstitutionTraitInfo; class Substitution : public EidosDictionaryRetained { @@ -49,7 +62,6 @@ 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 @@ -58,14 +70,17 @@ class Substitution : public EidosDictionaryRetained const slim_mutationid_t mutation_id_; // a unique id for each mutation, used to track mutations slim_usertag_t tag_value_; // a user-defined tag value + // Per-trait information + SubstitutionTraitInfo *trait_info_; // OWNED: a malloced block of per-trait information + Substitution(const Substitution&) = delete; // no copying Substitution& operator=(const Substitution&) = delete; // no copying Substitution(void) = delete; // no null construction Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick); // construct from the mutation that has fixed, and the tick in which it fixed - Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, 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); - // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline - inline virtual ~Substitution(void) override { } + // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though + inline virtual ~Substitution(void) override { free(trait_info_); trait_info_ = nullptr; } void PrintForSLiMOutput(std::ostream &p_out) const; void PrintForSLiMOutput_Tag(std::ostream &p_out) const; @@ -79,18 +94,20 @@ 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_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..37d9eecb --- /dev/null +++ b/core/trait.cpp @@ -0,0 +1,280 @@ +// +// trait.cpp +// SLiM +// +// Created by Ben Haller on 6/25/25. +// Copyright © 2025 Messer Lab, http://messerlab.org/software/. All rights reserved. +// + +#include "trait.h" +#include "community.h" +#include "species.h" + + +Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, double p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : + index_(-1), name_(p_name), type_(p_type), baselineOffset_(p_baselineOffset), + individualOffsetMean_(p_individualOffsetMean), individualOffsetSD_(p_individualOffsetSD), + directFitnessEffect_(p_directFitnessEffect), community_(p_species.community_), species_(p_species) +{ + _RecacheIndividualOffsetDistribution(); +} + +void Trait::_RecacheIndividualOffsetDistribution(void) +{ + // cache for the fast case of an individual-offset SD of 0.0 + if (individualOffsetSD_ == 0.0) + { + individualOffsetFixed_ = true; + individualOffsetFixedValue_ = static_cast(individualOffsetMean_); + } + else + { + individualOffsetFixed_ = false; + } +} + +Trait::~Trait(void) +{ + //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()); + + return static_cast(gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_); +} + +EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) +{ + // All of our strings are in the global registry, so we can require a successful lookup + switch (p_property_id) + { + // constants + case gID_index: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(index_)); + } + case gID_name: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(name_)); + } + case gID_species: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(&species_, gSLiM_Species_Class)); + } + case gEidosID_type: + { + static EidosValue_SP static_type_string_multiplicative; + static EidosValue_SP static_type_string_additive; + +#pragma omp critical (GetProperty_trait_type) + { + if (!static_type_string_multiplicative) + { + static_type_string_multiplicative = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("multiplicative")); + static_type_string_additive = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("additive")); + } + } + + switch (type_) + { + case TraitType::kMultiplicative: return static_type_string_multiplicative; + case TraitType::kAdditive: return static_type_string_additive; + default: return gStaticEidosValueNULL; // never hit; here to make the compiler happy + } + } + + // variables + case gID_baselineOffset: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(baselineOffset_)); + } + case gID_directFitnessEffect: + { + return (directFitnessEffect_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + } + case gID_individualOffsetMean: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetMean_)); + } + case gID_individualOffsetSD: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetSD_)); + } + case gID_tag: + { + slim_usertag_t tag_value = tag_value_; + + if (tag_value == SLIM_TAG_UNSET_VALUE) + EIDOS_TERMINATION << "ERROR (Trait::GetProperty): property tag accessed on trait before being set." << EidosTerminate(); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(tag_value)); + } + + // all others, including gID_none + default: + return super::GetProperty(p_property_id); + } +} + +void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) +{ + // All of our strings are in the global registry, so we can require a successful lookup + switch (p_property_id) + { + case gID_baselineOffset: + { + double value = p_value.FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(value)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property baselineOffset requires a finite value (not NAN or INF)." << EidosTerminate(); + + baselineOffset_ = value; + return; + } + case gID_directFitnessEffect: + { + bool value = p_value.LogicalAtIndex_NOCAST(0, nullptr); + + directFitnessEffect_ = value; + return; + } + case gID_individualOffsetMean: + { + double value = p_value.FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(value)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetMean requires a finite value (not NAN or INF)." << EidosTerminate(); + + individualOffsetMean_ = value; + _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 - + +EidosClass *gSLiM_Trait_Class = nullptr; + +const std::vector *Trait_Class::Properties(void) const +{ + static std::vector *properties = nullptr; + + if (!properties) + { + THREAD_SAFETY_IN_ANY_PARALLEL("Trait_Class::Properties(): not warmed up"); + + properties = new std::vector(*super::Properties()); + + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_baselineOffset, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_directFitnessEffect, false, kEidosValueMaskLogical | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_index, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_individualOffsetMean, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_individualOffsetSD, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_name, true, kEidosValueMaskString | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_species, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Species_Class))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_type, true, kEidosValueMaskString | kEidosValueMaskSingleton))); + + std::sort(properties->begin(), properties->end(), CompareEidosPropertySignatures); + } + + return properties; +} + +const std::vector *Trait_Class::Methods(void) const +{ + static std::vector *methods = nullptr; + + if (!methods) + { + THREAD_SAFETY_IN_ANY_PARALLEL("Trait_Class::Methods(): not warmed up"); + + methods = new std::vector(*super::Methods()); + + + std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); + } + + return methods; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/trait.h b/core/trait.h new file mode 100644 index 00000000..6b37b0d5 --- /dev/null +++ b/core/trait.h @@ -0,0 +1,146 @@ +// +// trait.h +// SLiM +// +// Created by Ben Haller on 6/25/25. +// Copyright (c) 2025 Benjamin C. Haller. All rights reserved. +// A product of the Messer Lab, http://messerlab.org/slim/ +// + +// This file is part of SLiM. +// +// SLiM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// SLiM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with SLiM. If not, see . + +/* + + The class Trait represents a phenotypic trait. More than one trait can be defined for a given species, and mutations can + influence the value of more than one trait. Traits can be multiplicative (typically a population genetics style of trait) + or additive (typically a quantitative genetics style of trait). + + */ + +#ifndef __SLiM__trait__ +#define __SLiM__trait__ + + +class Community; +class Species; + +#include "eidos_globals.h" +#include "slim_globals.h" +#include "eidos_class_Dictionary.h" + + +extern EidosClass *gSLiM_Trait_Class; + + +class Trait : public EidosDictionaryRetained +{ + // This class has its copy constructor and assignment operator disabled, to prevent accidental copying. + +private: + typedef EidosDictionaryRetained super; + +#ifdef SLIMGUI +public: +#else +private: +#endif + + int64_t index_; // the index of this trait within its species + std::string name_; // the user-visible name of this trait + TraitType type_; // multiplicative or additive + + // offsets + double baselineOffset_; + + 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, double p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); + ~Trait(void); + + inline __attribute__((always_inline)) int64_t Index(void) const { return index_; } + inline __attribute__((always_inline)) void SetIndex(int64_t p_index) { index_ = p_index; } // only from AddTrait() + inline __attribute__((always_inline)) TraitType Type(void) const { return type_; } + inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } + + void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ + slim_effect_t _DrawIndividualOffset(void) const; // draws from a normal distribution defined by individualOffsetMean_ and individualOffsetSD_ + inline slim_effect_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } + + + // + // Eidos support + // + virtual const EidosClass *Class(void) const override; + virtual void Print(std::ostream &p_ostream) const override; + + virtual EidosValue_SP GetProperty(EidosGlobalStringID p_property_id) override; + virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; + + virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; +}; + +class Trait_Class : public EidosDictionaryRetained_Class +{ +private: + typedef EidosDictionaryRetained_Class super; + +public: + Trait_Class(const Trait_Class &p_original) = delete; // no copy-construct + Trait_Class& operator=(const Trait_Class&) = delete; // no copying + inline Trait_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } + + virtual const std::vector *Properties(void) const override; + virtual const std::vector *Methods(void) const override; +}; + + +#endif /* defined(__SLiM__trait__) */ + + + + + + + + + + + + + + + + + + + + + + diff --git a/eidos/eidos_class_Object.cpp b/eidos/eidos_class_Object.cpp index 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..6df79bc0 100644 --- a/eidos/eidos_class_TestElement.cpp +++ b/eidos/eidos_class_TestElement.cpp @@ -78,8 +78,9 @@ EidosValue_SP EidosTestElement::GetProperty(EidosGlobalStringID p_property_id) return super::GetProperty(p_property_id); } -EidosValue *EidosTestElement::GetProperty_Accelerated__yolk(EidosObject **p_elements, size_t p_elements_size) +EidosValue *EidosTestElement::GetProperty_Accelerated__yolk(EidosGlobalStringID p_property_id, EidosObject **p_elements, size_t p_elements_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_elements_size); for (size_t element_index = 0; element_index < p_elements_size; ++element_index) @@ -105,8 +106,9 @@ void EidosTestElement::SetProperty(EidosGlobalStringID p_property_id, const Eido return super::SetProperty(p_property_id, p_value); } -void EidosTestElement::SetProperty_Accelerated__yolk(EidosObject **p_elements, size_t p_elements_size, const EidosValue &p_source, size_t p_source_size) +void EidosTestElement::SetProperty_Accelerated__yolk(EidosGlobalStringID p_property_id, EidosObject **p_elements, size_t p_elements_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { int64_t source_value = p_source.IntAtIndex_NOCAST(0, nullptr); diff --git a/eidos/eidos_class_TestElement.h b/eidos/eidos_class_TestElement.h index 0e01bff1..ea3ad144 100644 --- a/eidos/eidos_class_TestElement.h +++ b/eidos/eidos_class_TestElement.h @@ -69,8 +69,8 @@ class EidosTestElement : public EidosDictionaryRetained EidosValue_SP ExecuteMethod_squareTest(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated__yolk(EidosObject **p_elements, size_t p_elements_size); - static void SetProperty_Accelerated__yolk(EidosObject **p_elements, size_t p_elements_size, const EidosValue &p_source, size_t p_source_size); + static EidosValue *GetProperty_Accelerated__yolk(EidosGlobalStringID p_property_id, EidosObject **p_elements, size_t p_elements_size); + static void SetProperty_Accelerated__yolk(EidosGlobalStringID p_property_id, EidosObject **p_elements, size_t p_elements_size, const EidosValue &p_source, size_t p_source_size); }; class EidosTestElement_Class : public EidosDictionaryRetained_Class diff --git a/eidos/eidos_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.h b/eidos/eidos_globals.h index 0d99d5d1..558ee7e4 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -1330,7 +1330,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 e5a379a2..d7dd4d47 100644 --- a/eidos/eidos_interpreter.cpp +++ b/eidos/eidos_interpreter.cpp @@ -1275,7 +1275,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]; @@ -1373,7 +1384,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_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 11d458a2..01e4d887 100644 --- a/eidos/eidos_test.cpp +++ b/eidos/eidos_test.cpp @@ -1861,10 +1861,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"); @@ -1893,17 +1893,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_other.cpp b/eidos/eidos_test_functions_other.cpp index 81a31f9f..337e62e9 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..5897fcfb 100644 --- a/eidos/eidos_value.h +++ b/eidos/eidos_value.h @@ -951,7 +951,7 @@ class EidosValue_Object final : public EidosValue { private: typedef EidosValue super; - + protected: // singleton/vector design: values_ will either point to singleton_value_, or to a malloced buffer; it will never be nullptr // in the case of a zero-length vector, note that values_ will point to singleton_value_ with count_ == 0 but capacity_ == 1 @@ -987,10 +987,6 @@ class EidosValue_Object final : public EidosValue } void RaiseForClassMismatch(void) const; - // Provided to SLiM for the Mutation-pointer hack; see EidosValue_Object::EidosValue_Object() for comments - void PatchPointersByAdding(std::uintptr_t p_pointer_difference); - void PatchPointersBySubtracting(std::uintptr_t p_pointer_difference); - public: EidosValue_Object(void) = delete; // no default constructor EidosValue_Object& operator=(const EidosValue_Object&) = delete; // no copying @@ -1083,7 +1079,7 @@ class EidosValue_Object final : public EidosValue else reserve(capacity_ << 1); } - + void erase_index(size_t p_index); // a weak substitute for erase() inline __attribute__((always_inline)) EidosObject **data_mutable(void) { WILL_MODIFY(this); return values_; } // the accessors below should be used to modify, since they handle Retain()/Release() @@ -1108,7 +1104,12 @@ class EidosValue_Object final : public EidosValue void set_object_element_no_check_no_previous_RR(EidosObject *p_object, size_t p_index); // specifies retain/release, previous value assumed invalid from resize_no_initialize_RR void set_object_element_no_check_NORR(EidosObject *p_object, size_t p_index); // specifies no retain/release - friend void SLiM_IncreaseMutationBlockCapacity(void); // for PatchPointersByAdding() / PatchPointersBySubtracting() +private: + // See comments on EidosValue_Object::EidosValue_Object(). Note this is shared by all species, and in + // SLiMgui it is shared by all running simulations, so we need to be careful not to step on any toes. + static std::vector static_EidosValue_Object_Mutation_Registry; + + friend class MutationBlock; // so it can access the above registry; see IncreaseMutationBlockCapacity() }; inline __attribute__((always_inline)) void EidosValue_Object::push_object_element_CRR(EidosObject *p_object)