sim.engine.Repeat Maven / Gradle / Ivy
Show all versions of mason Show documentation
/*
Copyright 2012 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.engine;
import java.io.Serializable;
import sim.util.*;
import ec.util.*;
/**
Repeat is an abstract Steppable and Stoppable which generalizes the notion of repeated
Steppables. You can do simple repeated steppables directly in the Schedule (using
scheduleRepeating(...) ) but these steppables just repeat every N timesteps. If you want
to repeat with a more complex pattern, use this class instead. You could do it by hand, but
the objective of this class is to make it a simpler process.
Repeat takes a subsidiary Steppable, and each time the Repeat has step() called, it first
reschedules itself in the Schedule, then calls step() on the subsidiary Steppable.
Repeat defines an abstract method, getNextTime(...), which you must override to specify
the next timestep to repeat. Repeats are stoppable: if you call stop() on a Repeat that
is presently scheduled, it will not call step() on its subsidiary Steppable and will instead
drop out of the Schedule instead of rescheduling itself any more.
Let's say that you want to repeat on timesteps 0, 1, 3, 4, 6, 7, 9, 10, etc., that is,
jump 1, then jump 2, then jump 1, then 2, etc. The ordering will always be 1.
You could create a Repeated steppable as follows:
Steppable step = ...
Repeat repeat = new Repeat(step, 1)
{
double jump = 2;
protected double getNextTime(SimState state, double currentTime)
{
jump = (jump == 1 ? 2 : 1);
return currentTime + jump;
}
};
schedule.scheduleOnce(0.0, 1, repeat);
Repeat can also handle random numbers. For example, suppose you wanted to repeat
at the current time, plus 1.0, plus a random number chosen from a power law distribution
with an alpha of 3.0 and a cutoff of 0.5. Starting at timestep 0.0, ordering 1.
You could do this:
Steppable step = ...
Repeat repeat = new Repeat(step, 1)
{
protected double getNextTime(SimState state, double currentTime)
{
return currentTime + 1.0 + sim.util.distributions.Distributions.nextPowLaw(3.0, 0.5, state.random);
}
};
schedule.scheduleOnce(0.0, 1, repeat);
Like RandomSequence, Repeat might
be used in a multithreaded environment (inside a ParallelSequence, for example). In this
situation, you need to synchronize on the random number generator inside the getNextTime()
method like this:
Steppable step = ...
Repeat repeat = new Repeat(step, 1)
{
protected double getNextTime(SimState state, double currentTime)
{
synchronized(state.random)
{
return currentTime + 1.0 + sim.util.distributions.Distributions.nextPowLaw(3.0, 0.5, state.random);
}
}
};
schedule.scheduleOnce(0.0, 1, repeat);
You can also change the ordering. Ordinarily the Repeat reschedules your Steppable
under the same ordering that you passed into the constructor. But you can update that
(this is a rare need). For example, to increase the ordering by 1 each time:
Steppable step = ...
Repeat repeat = new Repeat(step, 1)
{
protected double getNextTime(SimState state, double currentTime)
{
setOrdering(getOrdering() + 1);
return currentTime + 1.0 + sim.util.distributions.Distributions.nextPowLaw(3.0, 0.5, state.random);
}
};
schedule.scheduleOnce(0.0, 1, repeat);
Note that some distributions in sim.util.Distribution require instances be maintained, rather than
just simple function calls (like nextPowLaw). You can do this too. For example, suppose you want
to reschedule at the current time, plus 1.0, plus a Poisson-distributed value with a mean of 10.0.
You could do this (note the "final" declaration):
Steppable step = ...
SimState state = ...
final sim.util.distributions.Poisson poisson = new sim.util.distributions.Poisson(10.0, state);
Repeat repeat = new Repeat(step, 1)
{
protected double getNextTime(SimState state, double currentTime)
{
return currentTime + 1.0 + poisson.nextInt();
}
};
schedule.scheduleOnce(0.0, 1, repeat);
Of course you might want the Repeat to also schedule itself at some random timestep initially as well.
At present Repeat does not support this via an API -- you have to do it manually. This is mostly
because the using API would usually be just as long as doing it by hand. But in the future we might provide
something if there is demand. Anyway, let's say you want to pick a uniform time in the future to schedule
initially, between 0.0 and 10.0, inclusive. Thereafter you want to reschedule using the Poisson distribution shown
earlier. You could do it like this:
Steppable step = ...
SimState state = ...
final sim.util.distributions.Poisson poisson = new sim.util.distributions.Poisson(10.0, state);
Repeat repeat = new Repeat(step, 1)
{
protected double getNextTime(SimState state, double currentTime)
{
return currentTime + 1.0 + poisson.nextInt();
}
};
schedule.scheduleOnce(state.random.nextDouble(true, true) * 10.0, 1, repeat);
*/
public abstract class Repeat implements Steppable, Stoppable
{
Steppable step; // if null, does not reschedule
Schedule.Key key = null;
int ordering;
public Repeat(final Steppable step, int ordering)
{
this.step = step;
this.ordering = ordering;
}
protected abstract double getNextTime(SimState state, double currentTime);
public synchronized void setOrdering(int val)
{
ordering = val;
}
public synchronized int getOrdering()
{
return ordering;
}
public synchronized void step(final SimState state)
{
if (step!=null)
{
try
{
if (key == null)
key = new Schedule.Key(state.schedule.getTime(), ordering); // could be BEFORE_SIMULATION if you're manually calling this
key.time = getNextTime(state, key.time);
if (key.time < Schedule.AFTER_SIMULATION)
state.schedule.scheduleOnce(key, this); // may return false if we couldn't schedule, which is fine
else return;
}
catch (IllegalArgumentException e)
{
e.printStackTrace(); // something bad happened
}
assert sim.util.LocationLog.set(step);
step.step(state);
assert sim.util.LocationLog.clear();
}
}
public synchronized void stop()
{
step = null;
}
public String toString() { return "Repeat[" + step + "]"; }
}