Overview
Before 0.3.0
The following are planned as part of the 0.3.0 release.
- Script incremental builds (done)
- Script attribute reporting (done)
- Documentation rewrite (done)
- Goal scheduling
- Attribute average values
- Node filtering for average values
Features
Goals
Scheduling goals are planned of the form cycles=N, attribute=>{name=>{op=>'max|min'},name=>{op=>'eq|ne',value=>x}}. Rescheduling will repeat through cycles and the schedule with the highest score will be returned. Only attribute-based goals seeking is planned at this time.
Documentation
Rewrite in progress starting with 0.2.3. The documentation in Schedule::Activity is very thorough and complete, though some historical information is outdated. Introductory and overview documentation is somewhat lacking. That and better organization is the goal of the rewrite, as well as consistent terminology.
Attributes
Attribute recomputation is supported starting with 0.2.2, primarily for merged annotation support. Attributes as "goals" is planned for 0.2.3--5.
Rudimentary attribute reporting from the commandline is supported starting with 0.2.2.
There is a subtle intricacy raised by the behavior of node filtering on average values: A configuration may fix/quiesce attributes via the increase-by-zero trick in every node, permitting some control over updates to the rolling average values. Would it be expected that the value is constant, hence the average updating "second by second", until such time as it is explicitly changed? The current model is that the average updates via the trapezoid rule when the next value update occurs. This works for any granularity (including non-integer valued times), but it is, in a sense, retroactive. This gives two alternate views of reality, where the attribute average suddenly flips during values assignment: In one view, the average is steadily approaching the current value as time increases; but in the other, that average is replaced when the next value is set.
Ideally there would be no such discrepancy (speaking mathematically), meaning that attribute values are fixed for the purpose of computing the rolling average, thru the current time minus epsilon. When a value change occurs, that would only affect the future value of the average and would not change the historical value. This is obviously different than the current interpretation. The module author currently has no scenarios that rely on this granularity for the average, and because node filtering does not currently support average value selection, this non-backwards-compatible change would likely have minimal impacts. It would still appear above with a version-specific, future update... once a decision is made.
Node filtering
Average value filtering needs to be defined. Value filtering is straightforward because the last recorded value is the current value for the attribute. Average value filtering, however, has three different interpretations of value that are being considered:
The currently stored "average" is the rolling average as computed with the most recent attribute update. That update could have occurred recently or far in the past, but the recorded y-value and average represent a consistent pair at that time.
If, however, the most recent value is also interpreted as the "current value" for the attribute, then the rolling average can be updated for the current time. That average can be different than the previously recorded average. For example, suppose an attribute is zero for times zero through ten, then the attribute becomes one. At time twenty, the stored average is one-half and the stored value is one; however, if the value is also "increased by zero" at time twenty, then the updated average would be three-quarters. For the sake of filtering, then, should the "average" be one-half, or is it steadily increasing toward one as time advances? It seems more realistic that it would be the latter interpretation, since this means that node filtering permits "eventual activation" as the average crosses a threshold.
But consider the very next step of the process. Suppose that the node, when the filter passes, is also going to decrease the attribute in the above example by one. This will occur precisely at time twenty, meaning that the average will be one-half. So, is it more realistic to say that the average "would be three quarters at time twenty if the attribute value is still one", or that the average "would be one-half at time twenty if the attribute is updated per the instructions in the node". Because the latter interpretation raises a logical paradox (if it passes the filter it wouldn't have passed the filter, but if it fails the filter it would have passed the filter)... it is likely that we'll choose the middle interpretation.
That is, the attribute value is fixed up to the current time minus epsilon, and thus the speculative average at that time can be computed and represents the rolling average as updated to the current time. If the chosen node adjusts the value, and thus the rolling average, that would only affect selection of future nodes.
Annotations
Annotation filtering. Some annotations may occur before an action but not when after certain other actions. Annotations should support node filtering for this situation. No timeline has been established.
Annotations may also benefit from a schedule-wide closeness configuration, but no timeline has been established.
Scheduling
Incremental scheduling is available in the module and from the commandline starting with 0.2.1. Attribute recomputation is supported for materialized annotations starting with 0.2.2. Ideally a finalized savefile could also store materialized annotations, but no timeline has been established.
Tension configuration
Rebuilt in 0.1.9 to provide better control over slack and buffer tensions separately. Behavior is established based on mathematical distribution, which is uniform stepwise leading to a skewed distribution for overall schedules. Tension settings affect path selection, but the scheduler will fail for the full path if there is insufficient slack/buffer to adjust the path to the goal time. This may change in the future, namely additional tension settings may permit scheduling completion even if the measured slack/buffer wouldn't otherwise permit adjustment. See the distribution in samples/tension.png and documentation in Schedule::Activity.
Tension should also be configurable in the outer scheduling step. This would permit incrementally relaxing the scheduler in case of retries. No timeline has been established.
Tutorial
A basic tutorial and sample configuration is provided starting with 0.2.0. It may be extended.
Action limits
The case limit=1 is not the primary/initial use case of this scheduler, but some events will likely never be repeated. This can already be achieved by setting the attribute to zero initially, incrementing as appropriate, and then filtering. Because there's a difference between limit-per-activity and limit-per-schedule, it's not clear that a special mechanism will be created.
Slack/buffer defaults
As an improvement, support passing slack/buffer ratios during configuration building. Eventually it will be helpful to pass slack/buffer defaults as part of schedule building: Having a value used during schedule building would permit relaxation during retries, and could be reported with the result. This requires changing assumptions in the validators.
Pre-scheduling safety
On by default in 0.1.9, but can be turned off with unsafe=1 and schedule-activity.pl --unsafe. More complex schedules may be difficult to interpret at a glance. One particular annoyance is finding that the target time for an activity cannot be reached, or that there are too many actions to fit a smaller goal window. Convenience functions find the minimum possible activity time (shortest path through the nodes, ultimately), if the activity can be completed at all (sorta easy to forget, and should be checked before attempting scheduling), and the maximum possible activity time (if defined). Moreover, these checks are needed to avoid hanging nodes (that don't reach finish), or action nodes with zero times (that never make progress), orphans, etc.
Script-based tooling
Available in 0.1.8, basic schedule-activity.pl script supports Dumper/JSON schedules and activity lists, compiling with/out safety checks. Future support may provide for common schedule types: Music playlists (high count, non-redundant nodes), exercise schedules (chunking, some repeats), chores/errands (small action count per activity/category), games (rounds, countdown reminders).
In 0.1.9, some annotation handling is provided. Incremental building is supported starting with 0.2.1.
Markdown
Basic Markdown support is likely to change in non-backwards-compatible ways. A written proposal might be posted to ensure support for the common use cases that would aide with faster definitions and imports.
Sample text to speech tool
An HTML+Javascript solution utilizing Web text to speech already exists to handle reminders for schedules of the form hh:mm:ss message # comment and could be provided in samples/.
Additional samples
First, something like a music playlist is similar to the base case here. This is a "large number of random choices" but "each is very fixed in time length". There are multiple potential actions to be randomized. Music has attributes (mood). Scheduling can be arranged in chunks (activities), so there are natural groupings for an arrangement. Scheduling requires Action limits, above, so currently this is slightly difficult and/or requires a helper import mechanism to build the schedules. Moreover, the slack/buffer mechanisms operate 'backwards', in the sense that music+pause periods are fixed and may be impossible to schedule within a fixed/given window of time (hence the common patterns of advertisements between songs and fade-in/out techniques). It's not clear that an arithmetic equivalent of fading will ever be supported in this library. Nevertheless, a "playlist import" type mechanism to build the configurations would be helpful.
Second, something like holiday activities. This is a "small number of choices" but "each is very fluid in length" and has limit=1. An import tool for this type of request should be rather easy, but Action limits are a prerequisite.
Third, scheduling of paired events is not straightforward. An example would be match-off lineups of N teams (eg fantasy leagues). That is less a concern of temporal scheduling and more about uniformity in the lineup. This is possible by running two simultaneous builders on the same configuration, with very fixed times, but may result in self-alignment of activities. It's not clear that this type of scheduling will ever be supported in this library.
Fourth, this may be useful as a test generation tool. The prove command already supports shuffling, but for load and performance testing it's often useful to rerun test workloads and workflows in seemingly random order. This mimics traffic patterns more accurately since similar requests can appear frequently. Nothing in the existing code mandates the unit of time as "seconds", and message objects can be used to represent sequential tests, or a queue potentially offloaded to a number of parallel workers.
Functional improvements
Annotation validation
Annotations need better pre-scheduling validation and better separation from the main scheduling code. Currently unclear of the best place to provide those helpers, as well as annotation-group-merging functions and the associated configurations.
Slackless/Bufferless configurations
Goals can never be met if slack/buffers are zero and the activity time requested doesn't exactly match action choices. This should likely fail with an error similar to the convenience functions mentioned above.
Backtracking behavior
The current mechanism is primarily for safety. Better backtracking support should be possible.
Performance
Both processing time and memory have been secondary considerations in initial versions. Some optimizations are possible, particularly with rolling averages and the main scheduling algorithm.