Class ScenarioChangeListener
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:
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
- pass a
ChangeListenerFactory
to aScenarioSet
orScenarioDescription
constructor, or - pass a ChangeListenerFactory as an argument to the
ScenarioDescription.analyze(ca.spatial.patchworks.ChangeListenerFactory, boolean)
method, or - pass a ScenarioChangeListener to the
Control.waitForProgress(int, double, String[], double[], ScenarioChangeListener)
method.
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 createChangeListenerFactory
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 theScenarioDescription.deactivateCategory(java.lang.String)
method to allow a
more comprehensive control on which TargetDescriptions to turn off.
TargetDescription
s 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
ConstructorsConstructorDescriptionCreate a ScenarioChangeListener object. -
Method Summary
Modifier and TypeMethodDescriptionvoid
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 theControl.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 theControl.waitForProgress(int, double)
method has been invoked with convergence criteria.Get a reference to the currently active ScenarioDescription object.void
init()
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.
-
Constructor Details
-
ScenarioChangeListener
Create a ScenarioChangeListener object. This constructor will be called automatically by the scenario manager.- Parameters:
sd
- Specify theScenarioDescription
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 theControl.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
This routine can be called from the scheduler when it pauses to check performance when theControl.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 theScenarioSet.setIterations(int)
iteration count (the period between assessing scenario completion and calling theevaluateBefore()
andevaluateAfter(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 currentScenarioDescription
.This method is called after both the
evaluateBefore()
andevaluateAfter(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
Print a message on the standard message stream. Override for custom behaviour. -
printError
Print a message on the standard error stream. Override for custom behaviour. -
getScenarioDescription
Get a reference to the currently active ScenarioDescription object.
-