Show:
/**
 * Selective thinning operation
 * @method selectiveThinning
 * @param {object} options
 *    @param {string} options.id A unique identifier for the thinning activity (default: 'selective_select_trees').
 *    @param {object|undefined} options.schedule schedule for the thinning (default: undefined).
 *    @param {string} options.mode mode of thinning, either 'simple' or 'dynamic' (default: 'simple').
 *    @param {string} options.SpeciesMode mode of species selection, either 'simple' or 'dynamic' (default: 'simple').
 *    @param {number|function} options.nTrees number of trees to select, can be a number or a function returning a number (default: 80).
 *    @param {number|function} options.nCompetitors number of competitor trees to select, can be a number or a function returning a number (default: 4).
 *    @param {object|function} options.speciesSelectivity object defining species selectivity, can be an object or a function returning an object (default: {}).
 *    @param {string|function} options.preferenceFunction ranking string for selecting trees, can be a string or a function returning a string (default: 'height').
 *    @param {number} options.interval interval between repeated thinnings (default: 5).
 *    @param {number} options.times number of times to repeat the thinning (default: 5).
 *    @param {string} options.sendSignal signal send out in each thinning activity (default: 'selective_thinning_remove').
 *    @param {string|undefined} options.constraint constraint (default: undefined).
 * @return {object} program - An object describing the thinning program
 * @example
 *     lib.thinning.selectiveThinning({
 *         schedule: { start: 30, end: 100 },
 *         mode: 'dynamic',
 *         SpeciesMode: 'dynamic',
 *         nTrees: function() { return stand.flag('nTrees') || 100; },
 *         nCompetitors: function() { return stand.flag('nCompetitors') || 5; }
 *     });
 */
lib.thinning.selectiveThinning = function(options) {
    // 1. Default Options
    const defaultOptions = {
        id: 'SelectiveThinning',
        schedule: undefined,
        mode: 'simple',
        nTrees: 80,
        nCompetitors: 4,
        speciesSelectivity: {},
        preferenceFunction: 'height',
        interval: 5,
        times: 5,
        sendSignal: 'selective_thinning_remove',
        constraint: undefined,

        // ... add other default thinning parameters
    };
    const opts = lib.mergeOptions(defaultOptions, options || {});


    // dynamic parameters of selective thinning
    function dynamic_nTrees() {
        // retrieve N from stand flag during runtime
        var value = stand.flag('nTrees');
        if (value === undefined) value = opts.nTrees;
        return value;
    };
    function dynamic_nCompetitors() {
        // retrieve N from stand flag during runtime
        //var value = stand.flag('nCompetitors');
        const Agefactor = Math.max(Math.min(1.0, -0.01*stand.age+1.2), 0.0);
        var value = Math.max(stand.flag('nCompetitors')*Agefactor, 1);
        if (value === undefined) value = opts.nCompetitors;
        return value;
    };

    // changing parameters if mode is dynamic
    if (opts.mode == 'dynamic') {
        opts.nTrees = dynamic_nTrees;
        opts.nCompetitors = dynamic_nCompetitors;
    };

    const program = {};

    program["Selective_selector"] = {
        id: opts.id + '_selector',
        type: 'thinning',
        schedule: opts.schedule,	
        thinning: 'selection',
        N: opts.nTrees,
        NCompetitors: opts.nCompetitors, // total competitors! not per thinning event
        speciesSelectivity: opts.speciesSelectivity,
        ranking: opts.preferenceFunction,

        onSetup: function() { 
            lib.initStandObj(); // create an empty object as stand property
        },
    
        onEnter: function() {
            stand.obj.lib.selective_thinning_counter = 0;
        },

        onExit: function() {
            //lib.activityLog(opts.id + ' - thinning_selection done'); 
        },
        description: `Part of selective thinning - mark ${opts.nTrees} crop trees and ${opts.nCompetitors} competitors.`
    };

    program['Selective_repeater'] = lib.repeater({ 
        id: opts.id + '_repeater',
        schedule: opts.schedule,
        signal: opts.sendSignal,
        interval: opts.interval,
        count: opts.times 
    });

    program["Selective_remover"] = {
        id: opts.id + '_remover',
        type: 'general',
        schedule: { signal: opts.sendSignal },
        action: function() {
            if (stand.obj.lib.selective_thinning_counter == 0) {
                // first year. Save # of marked competitors
                const marked = stand.trees.load('markcompetitor=true');
                stand.setFlag('compN', marked);
                lib.dbg(`selectiveThinning: start removal phase. ${marked} trees marked for removal (Target was ${opts.nCompetitors*opts.nTrees}).`);
            }
            stand.obj.lib.selective_thinning_counter = stand.obj.lib.selective_thinning_counter + 1;
            var n = stand.flag('compN') / opts.times;

            stand.trees.load('markcompetitor=true');
            stand.trees.filterRandomExclude(n);
            const harvested = stand.trees.harvest();
            //lib.activityLog('thinning remove competitors'); 
            lib.dbg(`selectiveThinning: repeat ${stand.obj.lib.selective_thinning_counter}, removed ${harvested} trees.`);
        },
        description: `Part of selective thinning - remove selected competitors in ${opts.times} activies every ${opts.interval} years.`
    }

    if (opts.constraint !== undefined) program.constraint = opts.constraint;

    return program;
}


lib.thinning.selectiveThinningZ1Z2 = function(options) {
    // 1. Default Options
    const defaultOptions = {
        id: 'selectiveThinningZ1Z2',
        schedule: undefined,
        mode: 'simple',
        SpeciesMode: 'simple',
        returnPeriode: 30, // years until the next generation of crop trees should be selected
        sendSignalPeriode: 'next_selection_periode', // signal, every time a new selection periode starts
        nTrees: 80, // number of crop trees to mark in each selection periode
        nCompetitors: 4, // total number of competitors to mark in each selection periode (not per thinning activity!)
        speciesSelectivity: {},
        preferenceFunction: 'height',
        times: 3, // number of thinning activities for each marked crop tree
        interval: 5, // years between each thinning activity
        sendSignalThinning: 'selective_start_thinning', // signal, at every thinning activity
        constraint: ["stand.age>30"],
        block: true,
        // ... add other default thinning parameters
    };
    const opts = lib.mergeOptions(defaultOptions, options || {});
    
    
    // dynamic parameters of selective thinning
    function dynamic_nTrees() {
      // retrieve N from stand flag during runtime
      var value = stand.flag('nTrees');
      if (value === undefined) value = opts.nTrees;
      return value;
    };
    function dynamic_nCompetitors() {
      // retrieve N from stand flag during runtime
      //var value = stand.flag('nCompetitors');
      const Agefactor = Math.max(Math.min(1.0, -0.01*stand.age+1.2), 0.0);
      var value = Math.max(stand.flag('nCompetitors')*Agefactor, 1);
      if (value === undefined) value = opts.nCompetitors;
      return value;
    };
    function dynamic_preferenceFunction() {
      // retrieve ranking from stand flag during runtime
      var value = stand.flag('preferenceFunction');
      if (value === undefined) value = opts.preferenceFunction;
      return value;
    };
    function dynamic_speciesSelectivity() {
      // retrieve species Selectivity from stand flag during runtime
      var value = stand.flag('speciesSelectivity');
      if (value === undefined) value = opts.speciesSelectivity;
      return value;
    };
    // changing parameters if mode is dynamic
    if (opts.mode == 'dynamic') {
      opts.nTrees = dynamic_nTrees;
      opts.nCompetitors = dynamic_nCompetitors;
      opts.preferenceFunction = dynamic_preferenceFunction;
    };
    // changing parameters if SpeciesMode is dynamic
    if (opts.SpeciesMode == 'dynamic') {
      opts.speciesSelectivity = dynamic_speciesSelectivity;
      //opts.preferenceFunction = dynamic_preferenceFunction;
    };
    
    const program = {};

    const initial_selection = {
        type: 'thinning',
        thinning: 'selection',
        id: opts.id + '_initial',
        schedule: opts.schedule,	
        constraint: opts.constraint,
        N: opts.nTrees,
        NCompetitors: opts.nCompetitors, 
        speciesSelectivity: opts.speciesSelectivity,
        ranking: opts.preferenceFunction,
    
        onSetup: function() { 
            lib.initStandObj(); // create an empty object as stand property
        },
    
        onEnter: function() {
            stand.obj.lib.selective_thinning_counter = 0;
        },
    
        onExecuted: function() {
            lib.dbg("Initial selection in stand " + stand.id + " executed.");
            stand.stp.signal(opts.sendSignalPeriode);            
            stand.stp.signal(opts.sendSignalThinning);
            //lib.activityLog('Initial crop tree selection'); 
        },
        description: `${opts.id} - initial selection of crop trees and competitors.`
    };
    program["SelectiveZ1Z2_initial_selection"] = initial_selection;
    
    program["SelectiveZ1Z2_repeater"] = lib.repeater({
        id: opts.id + "_selection_repeater",
        schedule: { signal: opts.sendSignalPeriode },
        signal: opts.sendSignalPeriode,
        count: 1000, // high number as it should go "forever" ;-)
        interval: opts.returnPeriode,
        block: opts.block,
    }); 
  
    const select_trees = {
        type: 'thinning',
        thinning: 'selection',
        id: opts.id + '_repeating',
        schedule: { signal: opts.sendSignalPeriode },	
        constraint: opts.constraint,
        N: opts.nTrees,
        NCompetitors: opts.nCompetitors, 
        speciesSelectivity: opts.speciesSelectivity,
        ranking: opts.preferenceFunction + ' * (height < 25)',
    
        onCreate: function(act) { 
            act.scheduled=false; /* this makes sure that evaluate is also called when invoked by a signal */ 
            console.log(`onCreate: ${opts.id}: `);
            printObj(this);
            console.log('---end---');							  
        },
    
        onEnter: function() {
            lib.dbg("Hello world");
            stand.obj.lib.selective_thinning_counter = 0;
        },
    
        onExecuted: function() {
            lib.dbg("Select trees in stand " + stand.id + " executed.");
			stand.stp.signal('selective_start_thinning');
            //lib.activityLog('thinning_selection'); 
        },
        description: `Selective thinning. Repeated ${opts.times} times every ${opts.interval} years.`
    };

    program["SelectiveZ1Z2_selector"] = select_trees;
  
    program['SelectiveZ1Z2_thinning_repeater'] = lib.repeater({ 
        id: opts.id + '_thinning_repeater',
        schedule: { signal: 'selective_start_thinning'},
        signal: 'selective_thinning_remove',
        interval: opts.interval,
        count: opts.times,
        block: opts.block,
    });
  
    const remove_trees = {
        type: 'general',
        id: opts.id + '_remove_trees',
        schedule: { signal: 'selective_thinning_remove'},
        action: function() {
            if (stand.obj.lib.selective_thinning_counter == 0) {
                // first year. Save # of marked competitors
                const marked = stand.trees.load('markcompetitor=true');
                stand.setFlag('compN', marked);
                lib.dbg(`selectiveThinning: start removal phase. ${marked} trees marked for removal.`);
            }
            
            lib.log("Year: " + Globals.year + ", selective thinning harvest");
      
            stand.obj.lib.selective_thinning_counter = stand.obj.lib.selective_thinning_counter + 1;
            var n = stand.flag('compN') / opts.times;
            var N_Competitors = stand.trees.load('markcompetitor=true');
      
            if ((N_Competitors - n) > 0) {
                stand.trees.filterRandomExclude(N_Competitors - n);
            };
      
            const harvested = stand.trees.harvest();
            //lib.activityLog('thinning remove competitors'); // details? target species?
            // stand.trees.removeMarkedTrees(); // ? necessary ??
            lib.dbg(`selectiveThinning: repeat ${stand.obj.lib.selective_thinning_counter}, removed ${harvested} trees.`);
        },
        description: `Selective thinning (every ${opts.times} years), that removes all trees above a target diameter ( ${opts.TargetDBH} cm)).`
    }
    program["SelectiveZ1Z2_remover"] = remove_trees;
  
    if (opts.constraint !== undefined) program.constraint = opts.constraint;
  
    return program;
}
  


lib.thinning.selectiveThinningBackup = function(options) {
    // 1. Default Options
    const defaultOptions = {
        schedule: undefined,
        id: 'SelectiveThinning',
        mode: 'simple',
        SpeciesMode: 'simple',
        nTrees: 80,
        nCompetitors: 4,
        speciesSelectivity: {},
        preferenceFunction: 'height',
        interval: 5,
        times: 5,
        sendSignal: undefined,
        constraint: undefined,

        // ... add other default thinning parameters
    };
    const opts = lib.mergeOptions(defaultOptions, options || {});


    // dynamic parameters of selective thinning
    function dynamic_nTrees() {
        // retrieve N from stand flag during runtime
        var value = stand.flag('nTrees');
        if (value === undefined) value = opts.nTrees;
        return value;
    };
    function dynamic_nCompetitors() {
        // retrieve N from stand flag during runtime
        //var value = stand.flag('nCompetitors');
        const Agefactor = Math.max(Math.min(1.0, -0.01*stand.age+1.2), 0.0);
        var value = Math.max(stand.flag('nCompetitors')*Agefactor, 1);
        if (value === undefined) value = opts.nCompetitors;
        return value;
    };
    function dynamic_preferenceFunction() {
        // retrieve ranking from stand flag during runtime
        var value = stand.flag('preferenceFunction');
        if (value === undefined) value = opts.preferenceFunction;
        return value;
    };

    function dynamic_speciesSelectivity() {
        // retrieve species Selectivity from stand flag during runtime
        var value = stand.flag('speciesSelectivity');
        if (value === undefined) value = opts.speciesSelectivity;
        return value;
    };

    // changing parameters if mode is dynamic
    if (opts.mode == 'dynamic') {
        opts.nTrees = dynamic_nTrees;
        opts.nCompetitors = dynamic_nCompetitors;
        opts.preferenceFunction = dynamic_preferenceFunction;
    };

    // changing parameters if SpeciesMode is dynamic
    if (opts.SpeciesMode == 'dynamic') {
        opts.speciesSelectivity = dynamic_speciesSelectivity;
        //opts.preferenceFunction = dynamic_preferenceFunction;
    };

    const program = {};

    program["Selective_selector"] = {
        id: opts.id + '_selector',
        type: 'thinning',
        schedule: opts.schedule,	
        thinning: 'selection',
        N: opts.nTrees,
        NCompetitors: opts.nCompetitors, // total competitors! not per thinning event
        speciesSelectivity: opts.speciesSelectivity,
        ranking: opts.preferenceFunction,

        onSetup: function() { 
            lib.initStandObj(); // create an empty object as stand property
        },
    
        onEnter: function() {
            stand.obj.lib.selective_thinning_counter = 0;
        },

        onExit: function() {
            stand.stp.signal('selective_start_repeat');
            //lib.activityLog('thinning_selection'); 
        },
        description: `Selective thinning. Repeated ${opts.times} times every ${opts.interval} years.`
    };

    program['Selective_repeater'] = lib.repeater({ 
        id: opts.id + '_repeater',
        schedule: { signal: 'selective_start_repeat'},
        signal: 'selective_thinning_remove',
        interval: opts.interval,
        count: opts.times 
    });

    program["Selective_remover"] = {
        id: opts.id + '_remover',
        type: 'general',
        schedule: { signal: 'selective_thinning_remove'},
        action: function() {
            if (stand.obj.lib.selective_thinning_counter == 0) {
                // first year. Save # of marked competitors
                const marked = stand.trees.load('markcompetitor=true');
                stand.setFlag('compN', marked);
                lib.dbg(`selectiveThinning: start removal phase. ${marked} trees marked for removal.`);
            }
            stand.obj.lib.selective_thinning_counter = stand.obj.lib.selective_thinning_counter + 1;
            var n = stand.flag('compN') / opts.times;


            lib.log("Year: " + Globals.year + ", selective thinning harvest");

            stand.trees.load('markcompetitor=true');
            stand.trees.filterRandomExclude(n);
            const harvested = stand.trees.harvest();
            //lib.activityLog('thinning remove competitors'); // details? target species?
            //stand.trees.removeMarkedTrees(); // ? necessary ??
            lib.dbg(`selectiveThinning: repeat ${stand.obj.lib.selective_thinning_counter}, removed ${harvested} trees.`);
        },
        description: `Selective thinning (every ${opts.times} years), that removes all trees above a target diameter ( ${opts.TargetDBH} cm)).`
    }

/*
    const remove_selection = {
        type: 'general',
        id: 'remove_trees',
        schedule: {repeat: true, repeatInterval: opts.interval, repeatTimes: opts.times},
        action: function() {
            //Globals.alert("Lets go");
            stand.setFlag('count',  stand.flag('count') + 1); // update counter
            var counter = stand.flag('count');
            if (counter == 1) {
                // first year. Save # of marked competitors
                stand.setFlag('compN', stand.trees.load('markcompetitor=true'));
            } /*
            if (counter > 4) {              // 4
                // stop repeating....
                Globals.alert('stop mgmt. ' + stand.activity.name);
                //stand.activityByName('remover').enabled=false;
                // activity: scheint bei repeating activities nicht richtig
                // activity: name
                //stand.activity.enabled = false;
                stand.activity.active = false;
                stand.wakeup(); // stop sleeping
                // force extra
                //stand.runNext(stand.activityByName('extra'));
            } * /
            var n = stand.flag('compN') / opts.times;

            console.log("Year: " + Globals.year + ", selective thinning harvest");

            stand.trees.load('markcompetitor=true');
            stand.trees.filterRandomExclude(n);
            stand.trees.harvest();
            stand.trees.removeMarkedTrees();
        },
        onSetup: function() {
            stand.setFlag('count', 0);
            stand.activity.active = false;
            stand.simulate = false;
        },
        onEnter: function() { stand.sleep(opts.times*opts.interval)}
    }

    program["remover"] = remove_selection; */

    if (opts.constraint !== undefined) program.constraint = opts.constraint;


    return program;
}

lib.thinning.selectiveThinningZ1Z2Backup = function(options) {
    // 1. Default Options
    const defaultOptions = {
        id: 'selectiveThinningZ1Z2',
        schedule: undefined,
        mode: 'simple',
        SpeciesMode: 'simple',
        returnPeriode: 30, // years until the next generation of crop trees should be selected
        signal: 'next_selection_periode', // signal, that the selection_repeater should emit
        nTrees: 80, // number of crop trees to mark in each selection periode
        nCompetitors: 4, // total number of competitors to mark in each selection periode (not per thinning activity!)
        speciesSelectivity: {},
        preferenceFunction: 'height',
        times: 3, // number of thinning activities for each marked crop tree
        interval: 5, // years between each thinning activity
        constraint: undefined,
        block: true,
        // ... add other default thinning parameters
    };
    const opts = lib.mergeOptions(defaultOptions, options || {});
    
    
    // dynamic parameters of selective thinning
    function dynamic_nTrees() {
      // retrieve N from stand flag during runtime
      var value = stand.flag('nTrees');
      if (value === undefined) value = opts.nTrees;
      return value;
    };
    function dynamic_nCompetitors() {
      // retrieve N from stand flag during runtime
      //var value = stand.flag('nCompetitors');
      const Agefactor = Math.max(Math.min(1.0, -0.01*stand.age+1.2), 0.0);
      var value = Math.max(stand.flag('nCompetitors')*Agefactor, 1);
      if (value === undefined) value = opts.nCompetitors;
      return value;
    };
    function dynamic_preferenceFunction() {
      // retrieve ranking from stand flag during runtime
      var value = stand.flag('preferenceFunction');
      if (value === undefined) value = opts.preferenceFunction;
      return value;
    };
    function dynamic_speciesSelectivity() {
      // retrieve species Selectivity from stand flag during runtime
      var value = stand.flag('speciesSelectivity');
      if (value === undefined) value = opts.speciesSelectivity;
      return value;
    };
    // changing parameters if mode is dynamic
    if (opts.mode == 'dynamic') {
      opts.nTrees = dynamic_nTrees;
      opts.nCompetitors = dynamic_nCompetitors;
      opts.preferenceFunction = dynamic_preferenceFunction;
    };
    // changing parameters if SpeciesMode is dynamic
    if (opts.SpeciesMode == 'dynamic') {
      opts.speciesSelectivity = dynamic_speciesSelectivity;
      //opts.preferenceFunction = dynamic_preferenceFunction;
    };
    
    const program = {};

    const initial_selection = {
        type: 'thinning',
        thinning: 'selection',
        id: opts.id + '_initial',
        schedule: opts.schedule,	
        constraint: ["stand.age>30"],
        N: opts.nTrees,
        NCompetitors: opts.nCompetitors, 
        speciesSelectivity: opts.speciesSelectivity,
        ranking: opts.preferenceFunction,
    
        onSetup: function() { 
            lib.initStandObj(); // create an empty object as stand property
        },
    
        onEnter: function() {
            stand.obj.lib.selective_thinning_counter = 0;
        },
    
        onExecuted: function() {
            lib.dbg("Initial selection in stand " + stand.id + " executed.");
            stand.stp.signal('start_selection_repeater');            
            stand.stp.signal('selective_start_thinning');
            //lib.activityLog('Initial crop tree selection'); 
        },
        description: `Selective thinning. Repeated ${opts.times} times every ${opts.interval} years.`
    };
    program["SelectiveZ1Z2_initial_selection"] = initial_selection;
    
    program["SelectiveZ1Z2_repeater"] = lib.repeater({
        id: opts.id + "_selection_repeater",
        schedule: { signal: 'start_selection_repeater' },
        signal: opts.signal,
        count: 1000, // high number as it should go "forever" ;-)
        interval: opts.returnPeriode,
        block: opts.block,
    }); 
  
    const select_trees = {
        type: 'thinning',
        thinning: 'selection',
        id: opts.id + '_repeating',
        schedule: { signal: opts.signal },	
        constraint: ["stand.age>30"],
        N: opts.nTrees,
        NCompetitors: opts.nCompetitors, 
        speciesSelectivity: opts.speciesSelectivity,
        ranking: opts.preferenceFunction + ' * (height < 25)',
    
        onCreate: function(act) { 
            act.scheduled=false; /* this makes sure that evaluate is also called when invoked by a signal */ 
            console.log(`onCreate: ${opts.id}: `);
            printObj(this);
            console.log('---end---');							  
        },
    
        onEnter: function() {
            lib.dbg("Hello world");
            stand.obj.lib.selective_thinning_counter = 0;
        },
    
        onExecuted: function() {
            lib.dbg("Select trees in stand " + stand.id + " executed.");
			stand.stp.signal('selective_start_thinning');
            //lib.activityLog('thinning_selection'); 
        },
        description: `Selective thinning. Repeated ${opts.times} times every ${opts.interval} years.`
    };

    program["SelectiveZ1Z2_selector"] = select_trees;
  
    program['SelectiveZ1Z2_thinning_repeater'] = lib.repeater({ 
        id: opts.id + '_thinning_repeater',
        schedule: { signal: 'selective_start_thinning'},
        signal: 'selective_thinning_remove',
        interval: opts.interval,
        count: opts.times,
        block: opts.block,
    });
  
    const remove_trees = {
        type: 'general',
        id: opts.id + '_remove_trees',
        schedule: { signal: 'selective_thinning_remove'},
        action: function() {
            if (stand.obj.lib.selective_thinning_counter == 0) {
                // first year. Save # of marked competitors
                const marked = stand.trees.load('markcompetitor=true');
                stand.setFlag('compN', marked);
                lib.dbg(`selectiveThinning: start removal phase. ${marked} trees marked for removal.`);
            }
            
            lib.log("Year: " + Globals.year + ", selective thinning harvest");
      
            stand.obj.lib.selective_thinning_counter = stand.obj.lib.selective_thinning_counter + 1;
            var n = stand.flag('compN') / opts.times;
            var N_Competitors = stand.trees.load('markcompetitor=true');
      
            if ((N_Competitors - n) > 0) {
                stand.trees.filterRandomExclude(N_Competitors - n);
            };
      
            const harvested = stand.trees.harvest();
            //lib.activityLog('thinning remove competitors'); // details? target species?
            // stand.trees.removeMarkedTrees(); // ? necessary ??
            lib.dbg(`selectiveThinning: repeat ${stand.obj.lib.selective_thinning_counter}, removed ${harvested} trees.`);
        },
        description: `Selective thinning (every ${opts.times} years), that removes all trees above a target diameter ( ${opts.TargetDBH} cm)).`
    }
    program["SelectiveZ1Z2_remover"] = remove_trees;
  
    if (opts.constraint !== undefined) program.constraint = opts.constraint;
  
    return program;
}