Class ScenarioChangeListener

java.lang.Object
ca.spatial.patchworks.ScenarioChangeListener

public class ScenarioChangeListener extends Object
Objects of this class listen for life cycle events that the scenario manager provides during the execution of a simulation. A ScenarioChangeListener can react to these events to initialize and control the simulation environment as the model is running, make decisions about termination, and to clean up and perform final tasks after the simulation is complete.

The listener has six life cycle methods: init(), evaluateBefore(), evaluateAfter(boolean, ca.spatial.patchworks.ScenarioMonitor), update(int, int), end(boolean) and wrapup(boolean). These methods are automatically invoked by the ScenarioSet manager at the corresponding places in the life cycle of the scenario:
Scenario life cycle

When the ScenarioSet manager is preparing to start a scenario it will first use the ChangeListenerFactory object to create a new ScenarioChangeListener object. After applying all declared TargetDescription objects and preparing the simulation environment the manager will then call the listeners init() method. The init method may take actions to modify the initial environment prior to starting the simulation. After the simulation starts the listeners evaluateBefore() and evaluateAfter(boolean,ca.spatial.patchworks.ScenarioMonitor) methods are called at intervals specified by the value of the scenario ScenarioDescription.getIterations() parameter. These methods provide a regularly timed opportunity to intervene in the simulation environment. The update(int, int) method is called more frequently allowing a more fine-grained control over target adjustment. After the scenario has completed the end(boolean) method is called to take care of any end of simulation requirements, such as customizing the reports to be produced. The ReportWriter.doAllReports(java.awt.Frame, java.lang.String, java.lang.String) method is autamatically called to save all of the reports, and finally the wrapup(boolean) method is called take care of any post-processing tasks.

Read the method descriptions below for more information about the role of each method.

In order to activate listeners to receive life cycle events

Some of the above methods make use of a ChangeListenerFactory rather than an instance of a ScenarioChangeListener. The ChangeListenerFactory interface is a thin wrapper that will instantiate a new listener each time it is invoked. This is needed so that each scenario gets a new unique listener. All of the examples below show the use of the ChangeListenerFactory.

When a scenario is started using the ScenarioSet.run() method the ChangeListenerFactory defined for the ScenarioDescription or ScenarioSet will be used to instantiate and attach a listener.

Only one listener may be attached to the scenario manager at a time. Any attempt to attach a second listener will result in an exception. Listeners will be automatically detached when the scenario life cycle has completed (stopping criteria met and wrapup(boolean) method completed). You may cancel a simulation and detach a listener by calling the Control.interrupt() method.

Examples

The following examples create ChangeListenerFactory objects. These factory objects create unique ScenarioChangeListeners on demand. The factory objects can be added to a ScenarioSet by using the factory parameter to the ScenarioSet constructor. For example:
 s = new ScenarioSet(improve, iterations, checkPointCount, makeZip,
                     subtargetPrefix, subtargetImprove, factory);
 

Example 1 - Post-process simulation output

This example shows a simple ScenarioChangeListener that performs a wrapup task of of calling a script to generate an animation. The makeAnimation script is available in the Patchworks script library and it makes use of the Patchworks API to create an animated GIF using the supplied parameters.

Note that only the wrapup method is declared in this listener. The other events not shown here will be handled by default methods (which will ignore the events).

 factory = new ChangeListenerFactory() {
   ScenarioChangeListener createListener(ScenarioDescription sd) {
     return new ScenarioChangeListener() {

       public void wrapup(boolean interrupted) {
         if (!interrupted) 
            makeAnimations("seralAnimatio.gif", 2, "maps/seral", "seral*.png");
       }
     };
   }
 }
 

Example 2 - Defer target activation by name

This example de-activates flow targets for the first pass, and then re-activates them once the solution has started to form. This step is often needed to overcome the initial entropy that exists when trying to balance flows.

Note that the deactivation only occurs when running in SOLVE mode. The RESUME and CONTINUE modes start with an existing allocation and deactivation may cause the initial solution to drift significantly. The SOLVE_NODEACTIVATE mode is a cue to not implement target deactivation.

In the deactivation pass the method will examine all defined targets. Those targets that are active and have labels starting with "flow." will be deactivated and added to a list. In the reactivation pass the targets in the list will be reactivated.

 factory = new ChangeListenerFactory() {
   ScenarioChangeListener createListener(ScenarioDescription sd) {
     return new ScenarioChangeListener() {
       int counter = 0;
       ArrayList flows = ArrayList();

       /**
        * De-activate the flow targets for the first pass.
        * This will allow the allocation to start to form before flow
        * targets are are imposed.  Otherwise, the flow constraints 
        * will be very binding during the initial allocation period 
        * and the model will be reluctant to allocate harvest.
        */
       public void init() {
          counter = 0;
          if (sd.getActivity() == ScenarioDescription.Actvity.SOLVE) {
             IProperties.logEvent("ScenarioSet", "De-activate flow targets");
             flows.clear();
             it = control.getTargetLabels().iterator();
             while(it.hasNext()) {
               t = control.getTarget(it.next());
               if (t.isActive() && t.getLabel().startsWith("flow.")) {
                 flows.add(t);
                 t.setActive(false);
               }
             }
          }
       }

       /**
        * Reactivate after the first pass through (several hundred thousand
        * iterations).  
        */
       public boolean evaluateBefore() {
           if (counter == 0) {
              if (sd.getActivity() == ScenarioDescription.Actvity.SOLVE) {
                 IProperties.logEvent("ScenarioSet", "Re-activate flow targets");
                 it = flows.iterator();
                 while(it.hasNext()) {
                    t = it.next();
                    t.setActive(true);
                 }
              }
           }
           return false;
       }
        
       /**
        * During the reactivation pass set the finished flag to false 
        * in order that the simulation will not finish prematurely.  Make 
        * sure that at least another pass occurs with the freshly
        * activated set of targets.
        */
       public boolean evaluateAfter(boolean finished, ScenarioMonitor monitor) {
           if (counter == 0) 
              finished = false;

           counter++;

           return finished;
        }
     };
   }
 }
 

Example 3 - Defer target activation by category

This third example is similar to the previous, but uses the ScenarioDescription.deactivateCategory(java.lang.String) method to allow a more comprehensive control on which TargetDescriptions to turn off. TargetDescriptions can also provide custom activation behaviour through the update method (see the documentation for TargetDescription for an example of how this might be implemented).
 factory = new ChangeListenerFactory() {
   ScenarioChangeListener createListener(ScenarioDescription sd) {
     return new ScenarioChangeListener() {
       int counter = 0;
       TargetDescription[] deactivatedTargets;

       /**
        * De-activate the some categories of targets for the first
        * pass. This will allow the allocation to start to form 
        * with treatment spread across all periods.  
        * Otherwise, these high entropy targets may cause the model to 
        * be very be reluctant to allocate harvest to any single period.
        * Once the allocation has an initial 'balance' the high entropy
        * targets may be turned back on.
        *
        * The regular expression specifies a case-insensitive match
        * to any of the alternatives ("flow" or "patch" or "road" or
        * "ratio").  For example, the regular expression will match to
        * categories of "Harvest Flow" or "VQO Ratios".
        */
       public void init() {
          counter = 0;
          if (sd.getActivity() == ScenarioDescription.Actvity.SOLVE) {
             IProperties.logEvent("ScenarioSet", "De-activate targets");
             deactivatedTargets = 
                sd.deactivateCategory("(?i).*(flow|patch|road|ratio).*");
          }
       }

       /**
        * Reactivate after the first pass through (several hundred thousand
        * iterations).  
        */
       public boolean evaluateBefore() {
           if (counter == 0) {
              if (sd.getActivity() == ScenarioDescription.Actvity.SOLVE) {
                 IProperties.logEvent("ScenarioSet", "Re-activate targets");
                 sd.activate(deactivatedTargets);
              }
           }

           return false;
       }
        
       /**
        * During the reactivation pass set the finished flag to false 
        * in order that the simulation will not finish prematurely.  Make 
        * sure that at least another pass occurs with the freshly
        * activated set of targets.
        */
       public boolean evaluateAfter(boolean finished, ScenarioMonitor monitor) {
           if (counter == 0) 
              finished = false;

           counter++;

           return finished;
        }
     };
   }
 }
 

Example 4 - Altering the schedule during execution

This example that sets up a listener to help remove small blocks on a periodic basis. In this example the a script named 'selectivelyCancelTreatmants' will take action to remove small unwanted harvest patches from the allocation. The goal in this scenario is to create large harvest patches, andintent of this intervention is to make bulk treatment schedule manipulations that will favour the formation of larger patches. Small patch allocations will be discarded across a number of planning periods and these stands will be available to be reallocated to more favourable arrangements. This approach may improve the formation of these desired arrangements.

The 'selectivelyCancelTreatmants' script will only start being executed after the evaluateAfter function has been called for the 20th time. The reason for the delay will be to allow the scheduler to start forming larger allocations before trimming off the smaller ones. The heuristic will continue to be executed for each event up until the 100th time. Beyond the 100th small patches will no longeer be canceled in order to allow the schedule to finalize and the simulation to complete.

The selectivelyCancelTreatments script is available in the Patchworks script library. It makes use of the Patchworks API to select the small patches within each planning period of interest and cancel the current treatment. Scripts such as this can make full use of the Patchworks API. Custom scripts can be defined within the ScenarioSet file or within an external script file so long as they have been 'sourced' within the current environment.

Many other custom meta-heuristics can be implemented in this manner to augment the built-in scheduling algorithms.

 factory = new ChangeListenerFactory() {
   ScenarioChangeListener createListener(ScenarioDescription sd) {
     return new ScenarioChangeListener() {
       int counter = 0;

       public void init() {
         counter = 0;
       }

       /**
        * The 'evaluateAfter' method is called every time the 
        * convergence criteria is checked.  For the first 19 passes
        * the method does nothing and allows the allocation to form.
        * During the 20th to 100th times that the method is called 
        * it will eliminate small blocks from allocation. Beyond 100
        * passes if the solution has not converged the algorithm will
        * allow small blocks to form.
        */
       public boolean evaluateAfter(boolean finished, ScenarioMonitor monitor) {
         counter++;
         if (!finished && (counter >= 20 && counter <= 100)) {
            print("Get rid of small fragments");
            selectivelyCancelTreatments("patch.harvest.size < 100", 
               new int[] {1,2,3,4,5,6,7,8,9,10,11,12});
         }

         return finished;
       }
     };
   }
 }
 

Example 5 - Passing user arguments

This example is a variation on the previous that passes arguments from the scenario definition. The original example hard-coded the lower and upper limit on the passes that would receive the treatment cancelling behaviour. In this example the ScenarioDescription is constructed with an added parameter: the limits to apply specified as a user defined argument. The ScenarioDescription might look like this (with lower and upper limits of 15 and 85):

 s.addScenario("scenarios/C5_Spatial2",
               "The Spatial2 builds on Spatial1 and adds harvest patches.",
               new TargetDescription[] {useVQO(1e12, true), 
                                        ndyGS(10000,0,true),  
                                        noDisturbancePatch, 
                                        harvestPatches(0.0001,true),
                                        deliveredHarvest(1,true),
                                        evenFlowMill(1000, true),
                                        roadBudget(1,true),
                                        useCompartAccess}
                ).setUserArg(new int[] {15, 85});

 

There is no restriction on the type of argument that can be passed using the ScenarioDescription.setUserArg(java.lang.Object) method. The argument to pass and the interpretation within the ScenarioChangeListener are completely up to the model designer.

 factory = new ChangeListenerFactory() {
   ScenarioChangeListener createListener(ScenarioDescription sd) {
     return new ScenarioChangeListener() {
       int counter = 0;

       /*
        * Get the lower and upper limits from the user argument.
        * Set default values in case the user argument was not
        * provided.
        */
       int lower = 20;
       int upper = 100;
       int[] args = sd.getUserArg();
       if (args != null) {
          lower = args[0];
          upper = args[1];
       }

       public void init() {
         counter = 0;
       }

       /**
        * The 'evaluateAfter' method is called every time the 
        * convergence criteria is checked.  The lower and upper limits 
        * on the periods to apply the cancelation method are passed
        * in as user arguments.
        */
       public boolean evaluateAfter(boolean finished, ScenarioMonitor monitor) {
         counter++;
         if (!finished && (counter >= lower && counter <= upper)) {
            print("Get rid of small fragments");
            selectivelyCancelTreatments("patch.harvest.size < 100", 
               new int[] {1,2,3,4,5,6,7,8,9,10,11,12});
         }

         return finished;
       }
     };
   }
 }
 
See Also:
ScenarioDescription.DefaultScenarioChangeListener, ScenarioDescription.analyze(ca.spatial.patchworks.ChangeListenerFactory, boolean), ScenarioDescription.analyzeWithListener(ca.spatial.patchworks.ScenarioChangeListener, boolean)
  • Constructor Summary

    Constructors
    Constructor
    Description
    Create a ScenarioChangeListener object.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    end​(boolean interrupted)
    This routine is called when the simulation is finished and before the reports have been saved.
    boolean
    evaluateAfter​(boolean finished, ScenarioMonitor monitor)
    This routine can be called from the scheduler when it pauses to check performance when the Control.waitForProgress(int, double) method has been invoked with convergence criteria.
    boolean
    This routine can be called from the scheduler when it pauses to check performance when the Control.waitForProgress(int, double) method has been invoked with convergence criteria.
    Get a reference to the currently active ScenarioDescription object.
    void
    This routine is called when the simulation is beginning after the TargetDescription objects have been applied and before the first iteration.
    void
    printError​(String message)
    Print a message on the standard error stream.
    void
    printMessage​(String message)
    Print a message on the standard message stream.
    void
    update​(int accepted, int attempted)
    This method is called by the scenario manager for each set of iterations that are processed.
    void
    wrapup​(boolean interrupted)
    This routine is called after the simulation is finished and after the reports have been saved.

    Methods inherited from class java.lang.Object

    equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • ScenarioChangeListener

      public ScenarioChangeListener(ScenarioDescription sd)
      Create a ScenarioChangeListener object. This constructor will be called automatically by the scenario manager.
      Parameters:
      sd - Specify the ScenarioDescription to process life cycle events for. Must not be null.
  • Method Details

    • init

      public void init()
      This routine is called when the simulation is beginning after the TargetDescription objects have been applied and before the first iteration. The intent is to allow the listener to set up the environment, possibly by altering the target environment or by initializing variables.

      The default implementation does nothing.

    • evaluateBefore

      public boolean evaluateBefore()
      This routine can be called from the scheduler when it pauses to check performance when the Control.waitForProgress(int, double) method has been invoked with convergence criteria. It is called immediately prior to testing the convergence criteria.

      This routine can make changes to the simulation state. The changes are made before the convergence assessment and they will change the value of the objective function and possibly alter the length of the simulation.

      Returns:
      The function returns a boolean value to indicate if the simulation should terminate. If the function returns true then the scheduler will terminate immediately. The default value is to return false.
    • evaluateAfter

      public boolean evaluateAfter(boolean finished, ScenarioMonitor monitor)
      This routine can be called from the scheduler when it pauses to check performance when the Control.waitForProgress(int, double) method has been invoked with convergence criteria. It is called after the convergence criteria has been tested. A boolean argument is passed in and indicates if the convergence criteria test has succeeded and the simulation is about to terminate.

      This routine can make changes to the simulation state. These changes are made after the convergence assessment.

      The routine returns a boolean value to indicate if the simulation should terminate. This value will override the results of the convergence test. It is possible to terminate the simulation early, or to continue the simulation after the convergence test has been performed.

      The ScenarioMonitor object encapsulates the state of the simulation and the convergence criteria. This object can be inspected to assist in making decisions about continuing or modifying the simulation.

      Parameters:
      finished - A flag to indicate if the the objective function convergence test has succeeded.
      monitor - A monitor object that tracks performance criteria and progress for this scenario.
      Returns:
      The function returns a boolean value to indicate if the simulation should continue. If the function returns true then the scheduler will terminate immediately. The default implementation simply returns the finished input parameter.
    • end

      public void end(boolean interrupted)
      This routine is called when the simulation is finished and before the reports have been saved. The intent is to allow the listener to perform clean up actions, customize the final state of the simulation environment, or modify the set of reports to be saved. A typical action is to pause the scheduler.

      The default implementation does nothing.

      Parameters:
      interrupted - Indicates if the scenario processing terminated normally or was interrupted by a user request or a processing error.
    • wrapup

      public void wrapup(boolean interrupted)
      This routine is called after the simulation is finished and after the reports have been saved. The intent is to produce the scenario reports and allow the listener to run scripts to post-process the scenario output. This may include functions like creating animation, extracting core areas or executing other GIS operations.

      The default implementation does nothing.

      Parameters:
      interrupted - Indicates if the scenario processing terminated normally or was interrupted by a user request or a processing error.
    • update

      public void update(int accepted, int attempted)
      This method is called by the scenario manager for each set of iterations that are processed. Typically this will be every 5,000 or 10,000 iterations, but this depends on the complexity of the targets that have been set. This is more frequently than the ScenarioSet.setIterations(int) iteration count (the period between assessing scenario completion and calling the evaluateBefore() and evaluateAfter(boolean, ca.spatial.patchworks.ScenarioMonitor) methods), which is typically set to several hundred thousand iterations.

      The purpose of this method is to provide an alternate fine-granied control on adjustment to target values as scenarios are run. The default behaviour is to call update on each of the TargetDescription objects that are in the current ScenarioDescription.

      This method is called after both the evaluateBefore() and evaluateAfter(boolean, ca.spatial.patchworks.ScenarioMonitor) methods have been called. It is not called if the scenario is interupted or if the scenario is terminated due to the acceptance criteria being met.

      Parameters:
      accepted - The number of permutations that were accepted in this interval.
      attempted - The number of permutations that were attempted in this interval.
    • printMessage

      public void printMessage(String message)
      Print a message on the standard message stream. Override for custom behaviour.
    • printError

      public void printError(String message)
      Print a message on the standard error stream. Override for custom behaviour.
    • getScenarioDescription

      public ScenarioDescription getScenarioDescription()
      Get a reference to the currently active ScenarioDescription object.