@@ -2664,6 +2664,275 @@ void _RunSLiMEidosBlockTests(void)
26642664 SLiMAssertScriptRaise (tickexpr9, " unrecognized function name tuck" , __LINE__);
26652665}
26662666
2667+ #pragma mark mateChoice() callback tests
2668+ void _RunMateChoiceTests (void )
2669+ {
2670+ // With the multitrait work, I completely redesigned how mateChoice() callbacks work under the hood. They
2671+ // should be much faster, but their logic is a bit tricky, so I'm adding a raft of new tests.
2672+
2673+ // This script tags everybody, and marks one individual as the preferred mate. It then confirms that that
2674+ // mate was chosen after mating has completed. This is the basis of all the mateChoice() tests here.
2675+ std::string verifiableMating1 (R"V0G0N(
2676+ initialize() {}
2677+ 1 early() {
2678+ sim.addSubpop("p1", 50);
2679+ }
2680+ early() {
2681+ p1.individuals.tag = 0;
2682+ p1.sampleIndividuals(1).tag = 1;
2683+ }
2684+ mateChoice() {
2685+ return p1.subsetIndividuals(tag=1);
2686+ }
2687+ modifyChild() {
2688+ child.tag = parent2.tag;
2689+ return T;
2690+ }
2691+ 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); }
2692+ )V0G0N" );
2693+ SLiMAssertScriptSuccess (verifiableMating1);
2694+
2695+ // add a no-op mateChoice() callback before the main one
2696+ std::string verifiableMating2 (R"V0G0N(
2697+ initialize() {}
2698+ 1 early() {
2699+ sim.addSubpop("p1", 50);
2700+ }
2701+ early() {
2702+ p1.individuals.tag = 0;
2703+ p1.sampleIndividuals(1).tag = 1;
2704+ }
2705+ mateChoice() {
2706+ return NULL;
2707+ }
2708+ mateChoice() {
2709+ return p1.subsetIndividuals(tag=1);
2710+ }
2711+ modifyChild() {
2712+ child.tag = parent2.tag;
2713+ return T;
2714+ }
2715+ 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); }
2716+ )V0G0N" );
2717+ SLiMAssertScriptSuccess (verifiableMating2);
2718+
2719+ // choose a random mate, and then change our minds
2720+ std::string verifiableMating3 (R"V0G0N(
2721+ initialize() {}
2722+ 1 early() {
2723+ sim.addSubpop("p1", 50);
2724+ }
2725+ early() {
2726+ p1.individuals.tag = 0;
2727+ p1.sampleIndividuals(1).tag = 1;
2728+ }
2729+ mateChoice() {
2730+ return p1.sampleIndividuals(1);
2731+ }
2732+ mateChoice() {
2733+ return p1.subsetIndividuals(tag=1);
2734+ }
2735+ modifyChild() {
2736+ child.tag = parent2.tag;
2737+ return T;
2738+ }
2739+ 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); }
2740+ )V0G0N" );
2741+ SLiMAssertScriptSuccess (verifiableMating3);
2742+
2743+ // return a random weights vector, and then change our minds
2744+ std::string verifiableMating4 (R"V0G0N(
2745+ initialize() {}
2746+ 1 early() {
2747+ sim.addSubpop("p1", 50);
2748+ }
2749+ early() {
2750+ p1.individuals.tag = 0;
2751+ p1.sampleIndividuals(1).tag = 1;
2752+ }
2753+ mateChoice() {
2754+ return runif(subpop.individualCount);
2755+ }
2756+ mateChoice() {
2757+ return p1.subsetIndividuals(tag=1);
2758+ }
2759+ modifyChild() {
2760+ child.tag = parent2.tag;
2761+ return T;
2762+ }
2763+ 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); }
2764+ )V0G0N" );
2765+ SLiMAssertScriptSuccess (verifiableMating4);
2766+
2767+ // do some random action, and then change our minds
2768+ std::string verifiableMating5 (R"V0G0N(
2769+ initialize() {}
2770+ 1 early() {
2771+ sim.addSubpop("p1", 50);
2772+ }
2773+ early() {
2774+ p1.individuals.tag = 0;
2775+ p1.sampleIndividuals(1).tag = 1;
2776+ }
2777+ mateChoice() {
2778+ if (runif(1) < 0.3)
2779+ return float(0);
2780+ if (runif(1) < 0.3)
2781+ return p1.sampleIndividuals(1);
2782+ if (runif(1) < 0.3)
2783+ return runif(subpop.individualCount);
2784+ if (runif(1) < 0.3)
2785+ return weights;
2786+ return NULL;
2787+ }
2788+ mateChoice() {
2789+ return p1.subsetIndividuals(tag=1);
2790+ }
2791+ modifyChild() {
2792+ child.tag = parent2.tag;
2793+ return T;
2794+ }
2795+ 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); }
2796+ )V0G0N" );
2797+ SLiMAssertScriptSuccess (verifiableMating5);
2798+
2799+ // choose the right individual, then return a weights vector representing that individual
2800+ std::string verifiableMating6 (R"V0G0N(
2801+ initialize() {}
2802+ 1 early() {
2803+ sim.addSubpop("p1", 50);
2804+ }
2805+ early() {
2806+ p1.individuals.tag = 0;
2807+ p1.sampleIndividuals(1).tag = 1;
2808+ }
2809+ mateChoice() {
2810+ return p1.subsetIndividuals(tag=1);
2811+ }
2812+ mateChoice() {
2813+ return weights;
2814+ }
2815+ modifyChild() {
2816+ child.tag = parent2.tag;
2817+ return T;
2818+ }
2819+ 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); }
2820+ )V0G0N" );
2821+ SLiMAssertScriptSuccess (verifiableMating6);
2822+
2823+ // choose the right individual, then return a weights vector representing that individual
2824+ std::string verifiableMating7 (R"V0G0N(
2825+ initialize() {}
2826+ 1 early() {
2827+ sim.addSubpop("p1", 50);
2828+ }
2829+ early() {
2830+ p1.individuals.tag = 0;
2831+ p1.sampleIndividuals(1).tag = 1;
2832+ }
2833+ mateChoice() {
2834+ return p1.subsetIndividuals(tag=1);
2835+ }
2836+ mateChoice() {
2837+ return weights * runif(subpop.individualCount);
2838+ }
2839+ modifyChild() {
2840+ child.tag = parent2.tag;
2841+ return T;
2842+ }
2843+ 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); }
2844+ )V0G0N" );
2845+ SLiMAssertScriptSuccess (verifiableMating7);
2846+
2847+ // add a bit of stochasticity to the previous, so that the fly individual isn't always chosen
2848+ std::string verifiableMating8 (R"V0G0N(
2849+ initialize() {}
2850+ 1 early() {
2851+ sim.addSubpop("p1", 50);
2852+ }
2853+ early() {
2854+ p1.individuals.tag = 0;
2855+ p1.sampleIndividuals(1).tag = 1;
2856+ }
2857+ mateChoice() {
2858+ return p1.subsetIndividuals(tag=1);
2859+ }
2860+ mateChoice() {
2861+ return weights + runif(subpop.individualCount, max=0.0001);
2862+ }
2863+ modifyChild() {
2864+ child.tag = parent2.tag;
2865+ return T;
2866+ }
2867+ 1:100 late() { if (mean(p1.individuals.tag != 1) > 0.1) stop(); }
2868+ )V0G0N" );
2869+ SLiMAssertScriptSuccess (verifiableMating8);
2870+
2871+ // test using a global weights vector
2872+ std::string verifiableMating9 (R"V0G0N(
2873+ initialize() {}
2874+ 1 early() {
2875+ sim.addSubpop("p1", 50);
2876+ }
2877+ early() {
2878+ p1.individuals.tag = 0;
2879+ p1.sampleIndividuals(1).tag = 1;
2880+
2881+ defineGlobal("WEIGHTS", rep(0.0, p1.individualCount));
2882+ WEIGHTS[whichMax(p1.individuals.tag)] = 1.0;
2883+ }
2884+ mateChoice() {
2885+ return runif(subpop.individualCount);
2886+ }
2887+ mateChoice() {
2888+ return WEIGHTS;
2889+ }
2890+ modifyChild() {
2891+ child.tag = parent2.tag;
2892+ return T;
2893+ }
2894+ 1:100 late() { if (mean(p1.individuals.tag != 1) > 0.1) stop(); }
2895+ )V0G0N" );
2896+ SLiMAssertScriptSuccess (verifiableMating9);
2897+
2898+ // finally, try to trigger an illegal modification of the global weights vector
2899+ std::string verifiableMating10 (R"V0G0N(
2900+ initialize() {}
2901+ 1 early() {
2902+ sim.addSubpop("p1", 50);
2903+ }
2904+ early() {
2905+ p1.individuals.tag = 0;
2906+ p1.sampleIndividuals(1).tag = 1;
2907+
2908+ defineGlobal("WEIGHTS", rep(0.0, p1.individualCount));
2909+ WEIGHTS[whichMax(p1.individuals.tag)] = 1.0;
2910+ defineGlobal("CHECK", WEIGHTS * 2.0);
2911+ }
2912+ mateChoice() {
2913+ // first return the global, which should get shared into returned_weights
2914+ return WEIGHTS;
2915+ }
2916+ mateChoice() {
2917+ // then return a chosen individual, which should set chosen_mate
2918+ return p1.subsetIndividuals(tag=1);
2919+ }
2920+ mateChoice() {
2921+ // then use weights, forcing a new build into returned_weights
2922+ return weights * 10.0;
2923+ }
2924+ modifyChild() {
2925+ child.tag = parent2.tag;
2926+ return T;
2927+ }
2928+ 1:100 late() {
2929+ if (!identical(WEIGHTS * 2.0, CHECK)) stop();
2930+ if (mean(p1.individuals.tag != 1) > 0.1) stop();
2931+ }
2932+ )V0G0N" );
2933+ SLiMAssertScriptSuccess (verifiableMating10);
2934+ }
2935+
26672936
26682937
26692938
0 commit comments