@Chapter
    @Title { Ejection Chains }
    @Tag { ejection }
@Begin
Ejection chains are sequences of repairs that generalize the augmenting
paths from bipartite matching.  They are due to Glover @Cite { $glover1996 },
who applied them to the travelling salesman problem.
# In timetabling,
# they have been applied to nurse rostering @Cite { $dowsland1998 },
# teacher assignment @Cite { $kingston2008resource }, and time repair
# @Cite { $kim1997 }.  Reference @Cite { $kim1997 } is very cryptic,
# unfortunately.
@BeginSections

@Section
    @Title { Introduction }
    @Tag { ejection.intro }
@Begin
@LP
An ejection chain algorithm targets one defect and tries a set of
alternative @I repairs on it.  A repair could be a simple move or
swap, or something arbitrarily complex.  It removes the defect, but
may introduce new defects.  If no new defects of significant cost
appear, that is success.  If just one significant new defect appears,
the method calls itself recursively to try to remove it; in this way
a chain of coordinated repairs is built up.  If several significant
new defects appear, or the recursive call fails to remove the new
defect, it undoes the repair and continues with alternative repairs.
It can also try to remove all the new defects.
# , although that is not often useful.
@PP
Corresponding to the well-known function for finding an augmenting
path in a bipartite graph, starting from a given node, is this
function, formulated by the author, for `augmenting' (improving)
a solution, starting from a given defect:
@ID @C {
bool Augment(Solution s, Cost c, Defect d);
}
(KHE's interface is somewhat different to this.)  @C { Augment }
has precondition
@ID @C { cost(s) >= c && cost(s) - cost(d) < c }
If it can change @C { s } to reduce its cost to less than @C { c },
it does so and returns @C { true }; if not, it leaves @C { s }
unchanged and returns @C { false }.  The precondition implies that
removing @C { d } without adding new defects would be one way to
succeed.  Here is an abstract implementation of @C { Augment }:
@ID -1px @Break @C {
bool Augment(Solution s, Cost c, Defect d)
{
  repair_set = RepairsOf(d);
  for( each repair r in repair_set )
  {
    new_defect_set = Apply(s, r);
    if( cost(s) < c )
      return true;
    for( each e in new_defect_set )
      if( cost(s) - cost(e) < c && Augment(s, c, e) )
        return true;
    UnApply(s, r);
  }
  return false;
}
}
It begins by finding a set of ways that @C { d } could be repaired.
For each repair, it applies it and receives the set of new defects
introduced by that repair, looks for success in two ways, then if
neither of those works out it unapplies the repair and continues
by trying the next repair, returning @C { false } when all repairs
have been tried without success.
@PP
Success could come in two ways.  Either a repair reduces
@C { cost(s) } to below @C { c }, or some new defect @C { e } has
cost large enough to ensure that removing it alone would constitute
success, and a recursive call targeted at @C { e } succeeds.  Notice
that @C { cost(s) } may grow without limit as the chain deepens,
while there is a defect @C { e } whose removal would reduce the
solution's cost to below @C { c }.
@PP
The key observation that justifies the whole approach is this:
the new defects targeted by the recursive calls are not known to
have resisted attack before.  It might be possible to repair one
of them without introducing any new defects of significant cost.
@PP
The algorithm stops at the first successful chain.  An option
for finding the best successful chain rather than the first has
been withdrawn, because of design problems in combining it with
ejection trees (Section {@NumberOf ejection.trees}).  It is no
great loss:  it produced nothing remarkable, and ran slowly.
Another option, for limiting the disruption caused by the
repairs, has also been withdrawn.  It too was not very useful.
It can be approximated by limiting the maximum chain length,
as described next.
@PP
The tree searched by @C { Augment } as presented may easily
grow to exponential size, which is not the intention.  The
author has tried two methods of limiting its size, both of
which seem useful.  They may be used separately or together.
@PP
The first method is to limit the maximum chain length to a fixed
constant, perhaps 3 or 4.  The maximum length is passed as an
extra parameter to @C { Augment }, and reduced on each
recursive call, with value 0 preventing further recursion.
Not only is this attractive in itself, it also supports
@I { iterative deepening }, in which @C { Augment } is called
several times on the same defect, with the length parameter
increased each time.  Another idea is to use a small length on
the first iteration of the main loop (see below), and increase
it on later iterations.
@PP
The second method is the one used by augmenting paths
in bipartite matching.  Just before each call on
@C { Augment } from the main loop, the entire solution is
marked unvisited (by incrementing a global visit number,
not by traversing the entire solution).  When a repair
changes some part of the solution, that part is marked
visited.  Repairs that change parts of the solution that
are already marked visited are tabu.  This limits the
size of the tree to the size of the solution.
@PP
Given a solution and a list of its defects, the main loop cycles
through the list repeatedly, calling @C { Augment } on each
defect in turn, with @C { c } set to @C { cost(s) }.  When the
main loop exits, every defect has been tried at least once without
success since the most recent success, so no further successful
augments are possible, unless there is a random element within
@C { Augment }.  Under reasonable assumptions, this very clear-cut
stopping criterion ensures that the whole algorithm runs in
polynomial time, for the same reason that hill-climbing does.
@PP
When there are several defect types, several @C { Augment } algorithms
are needed, one for each defect type, dynamically dispatched on the
type.  The repairs are usually applied directly, rather than indirectly
via objects built to represent them.
@PP
Careful work is needed to maximize the effectiveness of ejection
chains.  Grouping together monitors that measure the same thing
is important, because it reduces the number of defects and increases
their cost, increasing the chance that a chain will be continued.
Individual repair operations should actually remove the defects that
they are called to repair (the framework does not check this), and
should do whatever seems most likely to avoid introducing new defects.
@End @Section

@Section
    @Title { Ejector construction }
    @Tag { ejection.ejectors }
@Begin
@LP
KHE offers @I { ejector } objects which provide a framework for
ejection chain algorithms, reducing the implementation burden to
writing just the augment functions.
# The framework uses visit
# numbers (Section {@NumberOf "solutions.top.visit.numbers"}), in
# the conventional way.
# @PP
# To support statistics gathering (Section {@NumberOf ejection.statistics}),
# a single ejector object may be re-used many times, even on different
# instances (although not in parallel---an ejector object is highly
# mutable and cannot be shared by two or more threads).  This makes
# it important to distinguish between those parts of the ejector which
# are constant throughout its lifetime, and those parts which vary from
# solve to solve.  This section is concerned with the constant parts.
# @PP
An ejector object, stored in a given arena @C { a }, is constructed by
a sequence of calls beginning with
@ID @C {
KHE_EJECTOR KheEjectorMakeBegin(char *schedules, HA_ARENA a);
}
followed by calls which load augment functions, as explained below,
and ending with
@ID @C {
void KheEjectorMakeEnd(KHE_EJECTOR ej);
}
The ejector is then ready to do some solving
(Section {@NumberOf ejection.solving}).  The two attributes are returned by
@ID @C {
char *KheEjectorSchedules(KHE_EJECTOR ej);
HA_ARENA KheEjectorArena(KHE_EJECTOR ej);
}
There is no function to delete an ejector; it is deleted when its
arena is deleted.
@PP
The @C { schedules } string consists of one or
more @I { major schedules } separated by semicolons:
@ID @C {
<major_schedule>;<major_schedule>; ... ;<major_schedule>
}
Each major schedule consists of one or more @I { minor schedules }
separated by commas:
@ID @C {
<minor_schedule>,<minor_schedule>, ... ,<minor_schedule>
}
Each minor schedule consists of a positive integer called its
@I { maximum length }, followed by either @C { "+" } or @C { "-" },
representing a Boolean value called its @I { may revisit }
attribute.  The maximum length may have the special value @C { "u" },
meaning unlimited.  For example, schedule string
@ID @C {
"1+;u-"
}
contains two major schedules.  The first, @C { "1+" }, has
one minor schedule, with 1 for maximum length and @C { true }
for may revisit; the second, @C { "u-" }, also has one
minor schedule, this time with unlimited for maximum length
and @C { false } for may revisit.
@PP
The entire main loop of the algorithm, which repeatedly tries to augment
out of each defect until no further improvements can be found, is
repeated once for each major schedule in order.  Within each main
loop, the augment for one defect is tried once for each minor
schedule of the current major schedule, until an augment succeeds
in reducing the cost of the solution or all minor schedules have
been tried.
@PP
The maximum length determines the maximum number of repairs allowed
on one chain.  Value 0 allows no repairs at all and is forbidden.
Value 1 allows augment calls from the main loop, but prevents them
from making recursive calls, producing a kind of hill climbing.
Value 2 allows the calls made from the main loop to make recursive
calls, but prevents those calls from recursing.  And so on.
@PP
When the may revisit attribute is @C { false }, each part of the
solution may be changed by at most one of the recursive calls made
while repairing a defect; when it is @C { true }, each part may be
changed by any number of them, although only once along any one chain.
@PP
It is not good to set the maximum length to a large value and may
revisit to @C { true } in the same minor schedule, because the
algorithm will then usually take exponential time.  But setting
the maximum length to a small constant, or setting may revisit to
@C { false }, or both, guarantees polynomial time.  Another
interesting idea is @I { iterative deepening }, in which several
maximum lengths are tried.  For example,
@ID @C {
"1+;2+;3+;u-"
}
tries maximum length 1, then 2, then 3, and finishes with unlimited
length.
@PP
A mentioned earlier, in between calling @C { KheEjectorMakeBegin }
and @C { KheEjectorMakeEnd }, the augment functions need to be
loaded.  These are written by the user, as described in
Section {@NumberOf ejection.augment}, and passed to the ejector
by calls to
@ID @C {
void KheEjectorAddAugment(KHE_EJECTOR ej, KHE_MONITOR_TAG tag,
  KHE_EJECTOR_AUGMENT_FN augment_fn, int augment_type);
void KheEjectorAddGroupAugment(KHE_EJECTOR ej, int sub_tag,
  KHE_EJECTOR_AUGMENT_FN augment_fn, int augment_type);
}
The first says that defects which are non-group monitors with tag
@C { tag } should be handled by @C { augment_fn }; the second says
that defects which are group monitors with sub-tag @C { sub_tag }
should be handled by @C { augment_fn }.  Here @C { sub_tag } must
be between 0 and 29 inclusive.  Any values not set are handled
by doing nothing, as though an unsuccessful attempt was made to
repair them.  Ejectors handle the polymorphic dispatch by defect
type.  The @C { augment_type } parameter is used by statistics
gathering (Section {@NumberOf ejection.statistics}), and may be
0 if statistics are not wanted.
@End @Section

@Section
    @Title { Ejector solving }
    @Tag { ejection.solving }
@Begin
@LP
Once an ejector has been set up, the ejection chain algorithm
may be run by calling
@ID @C {
bool KheEjectorSolve(KHE_EJECTOR ej, KHE_GROUP_MONITOR start_gm,
  KHE_GROUP_MONITOR continue_gm, KHE_OPTIONS options);
}
This runs the main loop of the ejection chain algorithm once for each
major schedule, returning @C { true } if it reduces the cost of the
solution monitored by @C { start_gm } and @C { continue_gm }.
@PP
The main loop repairs only the defective child monitors of
@C { start_gm }, and the recursive calls repair only the
defective child monitors of @C { continue_gm }.  These two
group monitors could be equal, and either or both could be an
upcast solution.  Although it is not required, in practice
every child monitor of @C { start_gm } is also a child monitor
of @C { continue_gm }.
@PP
Just as an ejector is constructed by a sequence of calls enclosed
in @C { KheEjectorMakeBegin } and @C { KheEjectorMakeEnd },
so a solve is carried out by a sequence of calls beginning with
@ID @C {
void KheEjectorSolveBegin(KHE_EJECTOR ej, KHE_GROUP_MONITOR start_gm,
  KHE_GROUP_MONITOR continue_gm, KHE_OPTIONS options);
}
and ending with
@ID @C {
bool KheEjectorSolveEnd(KHE_EJECTOR ej);
}
@C { KheEjectorSolveEnd } does the actual solving.  Function
@C { KheEjectorSolve } above just calls @C { KheEjectorSolveBegin }
and @C { KheEjectorSolveEnd } with nothing in between.
@PP
The only functions callable between @C { KheEjectorSolveBegin }
and @C { KheEjectorSolveEnd } (at least, the only ones that change
anything) are
@ID @C {
void KheEjectorAddMonitorCostLimit(KHE_EJECTOR ej,
  KHE_MONITOR m, KHE_COST cost_limit);
void KheEjectorAddMonitorCostLimitReducing(KHE_EJECTOR ej,
  KHE_MONITOR m);
}
The call @C { KheEjectorAddMonitorCostLimit(ej, m, cost_limit) } says that
for a chain to end successfully, not only must the solution cost
be less than the initial cost, but @C { KheMonitorCost(m) } must
be no larger than @C { cost_limit }.
@C { KheEjectorAddMonitorCostLimitReducing(ej, m) } is the same
except that the cost limit is initialized to @C { KheMonitorCost(m) },
and if a successful chain is found and applied which reduces
@C { KheMonitorCost(m) } to below its current limit, that limit is
reduced to the new @C { KheMonitorCost(m) } for subsequent chains.
@PP
To visit these @I { limit monitors }, call
@ID @C {
int KheEjectorMonitorCostLimitCount(KHE_EJECTOR ej);
void KheEjectorMonitorCostLimit(KHE_EJECTOR ej, int i,
  KHE_MONITOR *m, KHE_COST *cost_limit, bool *reducing);
}
The returned values are the monitor, its current cost limit, and
whether that limit may be reduced.  Any number of limit monitors
may be added, but large numbers will not be efficient.
# Instead, a group monitor should be made out of
# them and its cost limited.  Although it is not quite the same
# thing, in practice it is just as good.  The same monitor cost
# limits apply to all schedules.
@PP
Each time the ejector enters the main loop, it makes a copy of
@C { start_gm }'s list of defects and sorts the copy by decreasing
cost.  Ties are broken differently depending on the value of the
solution's diversifier.  If the @C { es_limit_defects } option is
set to some integer (not to @C { "unlimited" }), defects are
dropped from the end of the sorted list to ensure that there
are no more than @C { es_limit_defects } of them.
@PP
Consider a defect @M { d } that the main loop of the ejection chain
solver is just about to attempt to repair.  Suppose that the most
recent change either to the solution or to the major schedule occurred
before the most recent previous attempt to repair @M { d }.  Then, if
the repair is deterministic, the current attempt to repair @M { d }
is certain to fail like the previous attempt did.  Accordingly, it
is skipped.  The implementation of this optimization uses visit
numbers stored in monitors.
@PP
In practice, repairs are not deterministic, since, for diversity,
KHE's augment functions vary the starting points of their traversals
of lists of repairs between calls.  However, the author carried out an
experiment on a large instance (NL-KP-03), in which this optimization
was turned off but a check was made to see whether there were any
cases where repairs which it would have caused to be skipped were
successful.  Over 8 diversified solves there were 15 cases.
@PP
The following functions may be called while @C { KheEjectorSolve }
is running (that is, from within augment functions):
@ID {0.95 1.0} @Scale @C {
KHE_GROUP_MONITOR KheEjectorStartGroupMonitor(KHE_EJECTOR ej);
KHE_GROUP_MONITOR KheEjectorContinueGroupMonitor(KHE_EJECTOR ej);
KHE_OPTIONS KheEjectorOptions(KHE_EJECTOR ej);
KHE_FRAME KheEjectorFrame(KHE_EJECTOR ej);
KHE_EVENT_TIMETABLE_MONITOR KheEjectorEventTimetableMonitor(KHE_EJECTOR ej);
KHE_SOLN KheEjectorSoln(KHE_EJECTOR ej);
KHE_COST KheEjectorTargetCost(KHE_EJECTOR ej);
bool KheEjectorCurrMayRevisit(KHE_EJECTOR ej);
int KheEjectorCurrLength(KHE_EJECTOR ej);
int KheEjectorCurrAugmentCount(KHE_EJECTOR ej);
bool KheEjectorCurrDebug(KHE_EJECTOR ej);
int KheEjectorCurrDebugIndent(KHE_EJECTOR ej);
}
# KHE_EJECTOR_MAJOR_SCHEDULE KheEjectorCurrMajorSchedule(KHE_EJECTOR ej);
# KHE_EJECTOR_MINOR_SCHEDULE KheEjectorCurrMinorSchedule(KHE_EJECTOR ej);
# int KheEjectorCurrDisruption(KHE_EJECTOR ej);
# KHE_TIME_PA RTITION KheEjectorTimePartition(KHE_EJECTOR ej);
{0.95 1.0} @Scale @C { KheEjectorStartGroupMonitor },
{0.95 1.0} @Scale @C { KheEjectorContinueGroupMonitor },
and
{0.95 1.0} @Scale @C { KheEjectorOptions } are @C { start_gm },
@C { continue_gm }, and @C { options } from @C { KheEjectorSolve }.
@C { KheEjectorFrame } is the value of
@C { KheFrameOption(options, "gs_common_frame", ins) },
for which see Section {@NumberOf extras.frames}.
@C { KheEjectorEventTimetableMonitor } is the value of the
@C { gs_event_timetable_monitor } option
(Section {@NumberOf general_solvers.general}), or @C { NULL }
if absent.  When it is present, augment functions may use it
to find the events running at a given time.
@PP
@C { KheEjectorSoln } is @C { start_gm }'s and @C { continue_gm }'s
solution.  @C { KheEjectorTargetCost }
is the cost that the chain needs to improve on (@C { c }
in the abstract code above):  the cost of the solution when
@C { Augment } was most recently called from the main loop.
# , except when ejection trees are in use, as explained in
# Section {@NumberOf ejection.trees}.
# @C { KheEjectorCurrMajorSchedule } is the current major schedule.
# @C { KheEjectorCurrMinorSchedule } is the current minor schedule, and
@C { KheEjectorCurrMayRevisit } is the
@C { may_revisit } attribute of the current minor schedule.
@PP
@C { KheEjectorCurrLength } is 1 when the augment function was called
from the main loop, 2 when it was called from an augment function called
from the main loop, etc.
@PP
@C { KheEjectorCurrAugmentCount } is the number of augments since this
solve began.  @C { KheEjectorCurrDebug } returns @C { true } when @C { ej }
is currently debugging, because it is trying to repair a main loop
defect in the monitor stored in the @C { gs_debug_monitor } option of
@C { options }.  It seems to work well for each repair to generate
a one-line description of itself when @C { KheEjectorCurrDebug } is
@C { true }.  @C { KheEjectorCurrDebugIndent } is the current amount
by which debug prints should be indented; this is twice the current
length.
#@PP
#A function which returns the solution that an ejector is for has been
#deliberately omitted, because an ejector may be applied to more than
#one solution during its lifetime (but never more than one in parallel).
#While solving, the expression
#@ID @C {
#KheMonitorSoln((KHE_MONITOR) KheEjectorStartGroupMonitor(ej))
#}
#returns the solution that @C { ej } is being applied to.
@End @Section

#@Section
#    @Title { How to write an augment function (current version) }
#    @Tag { ejection.augment }
#@Begin
#@LP
#An augment function has type
#@ID @C {
#typedef bool (*KHE_EJECTOR_AUGMENT_FN)(KHE_EJECTOR ej, KHE_MONITOR d,
#  KHE_TRACE tc, KHE_TRANSACTION tn);
#}
#The parameters are the ejector @C { ej } passed to @C { KheEjectorSolve },
#the defect @C { d } that the augment function is supposed to repair,
#a fresh trace object @C { tc } that the function must use to trace
#its repairs (@C { tc } traces @C { continue_gm }), and a fresh
#transaction object whose use is optional.  It is a precondition
#that @C { d } have non-zero cost and removing that cost would
#be a successful augment.
#@PP
#Augment functions have the following general form:
#@ID @C {
#bool MyAugment(KHE_EJECTOR ej, KHE_MONITOR d,
#  KHE_TRACE tc, KHE_TRANSACTION tn)
#{
#  for( each r in RepairsOf(d) )
#  {
#    KheTransactionBegin(tn);
#    KheTraceBegin(tc);
#    Apply(r);
#    KheTraceEnd(tc);
#    KheTransactionEnd(tn);
#    RepairsVisit(r);
#    if( KheEjector Success(ej, tc, disruption(r), 0, 0) )
#      return true;
#    KheTransactionUndo(tn);
#    if( KheEjectorCurrMayRevisit(ej) )
#      RepairsUnVisit(r);
#  }
#  return false;
#}
#}
#Function @C { RepairsOf } constructs a list of alternative repairs
#of @C { d }, being careful to exclude repairs that change any part of
#the solution marked visited (tested by calling @C { KheMeetVisited }
#etc. from Section {@NumberOf solutions.visit}).  The construction
#and application of repairs are often merged in practice.  Tracing
#is compulsory, but transactions are only needed for undoing, and
#@C { tn } need not be used in simple cases (but see the remarks in
#Section {@NumberOf ejection.extended} on undoing when solve types
#other than @C { KHE_EJECTOR_FIRST } are used).
#@PP
#Functions @C { RepairsVisit } and @C { RepairsUnVisit } stand for code
#which marks the entities changed by the repair visited and unvisited
#(by calling @C { KheMeetVisit } and @C { KheMeetUnVisit } etc. from
#Section {@NumberOf solutions.visit}).  Their placement in the above
#sample is the one to use when each repair changes different entities.
#More commonly, each repair changes the same entity, but in a different
#way, and in that case this code is better placed outside the repair
#loop.  Supposing, for example, that all repairs change only one part
#of the solution, namely @C { meet }, the code is
#@ID @C {
#if( !KheMeetVisited(meet) )
#{
#  KheMeetVisit(meet);
#  for( each r in RepairsOf(d) )
#     ...
#  if( KheEjectorCurrMayRevisit(ej) )
#    KheMeetUnVisit(meet);
#}
#}
#This way, only one recursive call can change @C { meet } (unless
#revisiting), but it can change it any number of times.  Visit
#numbers do not matter when @C { true } is returned, since that
#return propagates directly up to the main loop and is followed
#by a call on @C { Augment } with a new visit number.
#@PP
#@C { KheEjector Success } looks after testing whether success has
#occurred, and recursing:
#@ID @C {
#bool KheEjector Success(KHE_EJECTOR ej, KHE_TRACE tc, int disruption,
#  int augment_type, int repair_type);
#}
#It is passed the ejector @C { ej }, a trace object @C { tc } containing
#the newly introduced defects, the user's estimate of the amount of
#disruption the repair caused (Section {@NumberOf ejection.extended}),
#and @C { augment_type } and @C { repair_type }
#(Section {@NumberOf ejection.statistics}), which may be 0 if
#statistics are not wanted.  Here is its implementation,
#omitting code for handling limits on length, disruption, monitor
#costs, solve types other than @C { KHE_EJECTOR_FIRST },
#and statistics, but otherwise truthful:
#@ID @C {
#bool KheEjector Success(KHE_EJECTOR ej, KHE_TRACE tc, int disruption,
#  int augment_type, int repair_type)
#{
#  KHE_MONITOR m;  int i;  KHE_COST max_improvement;
#  if( KheSolnCost(ej->soln) < ej->target_cost )
#    return true;
#  for( i = 0;  i < KheTraceMonitorCount(tc);  i++ )
#  {
#    m = KheTraceMonitor(tc, i);
#    max_improvement = KheMonitorCost(m) - KheMonitorLowerBound(m);
#    if( KheMonitorCost(m) > KheTraceMonitorInitCost(tc, i) &&
#        KheSolnCost(ej->soln) - max_improvement < ej->target_cost &&
#	KheEjectorPolyAugment(ej, m) )
#      return true;
#  }
#  return false;
#}
#}
#It follows the abstract implementation (Section {@NumberOf ejection.intro}).
#It checks that any defect being recursed on did increase in cost, and does
#not try to find an improbable improvement of @C { KheMonitorCost(m) } to
#below @C { KheMonitorLowerBound(m) } (Section {@NumberOf monitoring_monitors}).
#The main loop checks this too.  Private function @C { KheEjectorPolyAugment }
#performs the polymorphic dispatch to the user's augment functions.  If
#a repair changes nothing, @C { KheEjector Success } returns @C { false },
#because the cost will not have decreased and the trace will contain no
#monitors.  There is no check that the repair that called
#@C { KheEjector Success } did actually reduce the cost of the defect
#it was given.
#@End @Section

@Section
    @Title { How to write an augment function }
    @Tag { ejection.augment }
@Begin
@LP
An augment function has type
@ID @C {
typedef bool (*KHE_EJECTOR_AUGMENT_FN)(KHE_EJECTOR ej, KHE_MONITOR d);
}
The parameters are the ejector @C { ej } passed to @C { KheEjectorSolve },
and the defect @C { d } that the augment function is supposed to repair.
It is a precondition that @C { d }'s cost exceeds its lower bound, and
that reducing its cost to its lower bound would be a step towards a
successful augment.  The return value says whether the augment
successfully reduced the solution cost or not, and must be the value
returned by the most recent call to @C { KheEjectorRepairEnd } below,
or @C { false } if there have been no repairs.  The ejector relies on
this value and it must be right.
@PP
Augment functions often look like this, although not necessarily exactly:
@ID @C {
bool ExampleAugment(KHE_EJECTOR ej, KHE_MONITOR d)
{
  KHE_ENTITY e;  bool success;  REPAIR r;
  e = SomeSolnEntityRelatedTo(d);
  if( !KheEntityVisited(e) )
  {
    KheEntityVisit(e);
    for( each r in RepairsOf(e) )
    {
      KheEjectorRepairBegin(ej);
      success = Apply(r);
      if( KheEjectorRepairEnd(ej, 0, success) )
	return true;
    }
    if( KheEjectorCurrMayRevisit(ej) )
      KheEntityUnVisit(e);
  }
  return false;
}
}
Function @C { SomeSolnEntityRelatedTo } uses @C { d } to identify
some entity (node, meet, task, etc.) that will be changed by the
repairs, but that should only be changed if it has not already
been visited (tested by calling @C { KheMeetVisited } etc. from
Section {@NumberOf "solutions.top.visit.numbers"}).  After the
visit, the boilerplate code
@ID @C {
if( KheEjectorCurrMayRevisit(ej) )
  KheEntityUnVisit(e);
}
marks the entity unvisited if revisiting is allowed.
@PP
Since writing the above, it has occurred to the author that the
visited entity related to @C { d } could be @C { d } itself.  The
code above that relates to visiting @C { e } is now applied to
@C { d } behind the scenes in the ejector object.  This has the
usual effect of prohibiting exponential searches, and it means
that the writer of an augment function can drop all visiting code
if desired, producing this:
@ID @C {
bool ExampleAugment(KHE_EJECTOR ej, KHE_MONITOR d)
{
  bool success;  REPAIR r;
  for( each r in RepairsOf(d) )
  {
    KheEjectorRepairBegin(ej);
    success = Apply(r);
    if( KheEjectorRepairEnd(ej, 0, success) )
      return true;
  }
  return false;
}
}
The augment function is also conceptually simpler this way:  it
searches a graph whose nodes are defects and whose edges are repairs,
without revisiting any node.  Of course, the search is different this
way, and the question of whether it is better or worse must be decided
empirically.  The author has successfully removed all visiting code
from his nurse rostering augment functions:  running times are often
larger, but cost is nearly always lower, often significantly lower.
@PP
Function @C { RepairsOf } builds a set of alternative repairs @C { r }
of @C { e }, and @C { Apply(r) } stands for the code that applies
repair @C { r }.  In practice, repairs just need to be iterated
over and applied; an explicit set of them is not needed.  Nor is
there any need for the augment function to follow any particular
structure; anything that generates a sequence of pairs of calls
to @C { KheEjectorRepairBegin } and @C { KheEjectorRepairEnd }
is acceptable.
@PP
For example, some expensive repairs are only worth trying when
repairing a defect that is really present in the solution, not
introduced by a previous repair.  This can be effected by
@ID @C {
if( KheEjectorCurrLength(ej) == 1 )
{
  KheEjectorRepairBegin(ej);
  ...
  if( KheEjectorRepairEnd(ej, 0, success) )
    return true;
}
}
This works because, as mentioned earlier,
@C { KheEjectorCurrLength(ej) } returns 1 when the augment function
was called from the main loop, 2 when the augment function was called
by an augment function called from the main loop, and so on.
@PP
Functions @C { KheEjectorRepairBegin } and @C { KheEjectorRepairEnd }
are supplied by KHE:
@ID @C {
void KheEjectorRepairBegin(KHE_EJECTOR ej);
bool KheEjectorRepairEnd(KHE_EJECTOR ej, int repair_type, bool success);
}
# bool KheEjectorRepairEndLong(KHE_EJECTOR ej, int repair_type,
#   bool success,
#   void (*on_success_fn)(void *on_success_val), void *on_success_val);
# @C { KheEjectorRepairEnd } just calls @C { KheEjectorRepairEndLong }
# with values @C { NULL } and @C { NULL } for the last two parameters.
# If other values are wanted for them, @C { KheEjectorRepairEndLong }
# must be called.
# For @C { max_sub_chains } see Section {@NumberOf ejection.trees};
# for @C { save_and_sort } see Section {@NumberOf ejection.sortrepairs};
# For the effect of these two parameters, see
# and for @C { on_success_fn } and @C { on_success_val } see
# Section {@NumberOf ejection.adjustment}.
# `@C { KheEjectorRepairEnd }' means `@C { KheEjectorRepairEnd } or
# @C { KheEjectorRepairEndLong }' from here on.
# @PP
Calls to them must occur in matching pairs.  A call to
@C { KheEjectorRepairBegin }
informs @C { ej } that a repair is about to begin, and the matching
call to @C { KheEjectorRepairEnd } informs it that that repair has
just ended.  The repair is undone and redone as required behind the
scenes by @C { KheEjectorRepairEnd }, using marks and paths, so undoing
is not the user's concern.
@PP
The @C { repair_type } parameter of @C { KheEjectorRepairEnd } is
used to gather statistics about the solve
(Section {@NumberOf ejection.statistics}).  It may be 0 if statistics
are not wanted.
@PP
The @C { success } parameter tells the ejector whether the caller
thinks the current repair was successful (that is, ran to completion).
If it is @C { false }, the ejector undoes the partially completed
repair and forgets that it ever happened.  If it is @C { true },
the ejector checks whether the repair reduced the cost of the
solution, whether there is a single new defect worth recursing on,
and so on.  The writer of an augment function can forget that all
this is happening behind the scenes.
# @PP
# The @C { disruption } parameter is supposed to measure the amount
# of disruption caused by the repair.  This allows the ejector to
# prefer repairs which cause little disruption, although at present
# this idea is out of favour.
@PP
If @C { KheEjectorRepairEnd } returns @C { true }, the ejector has
found a successful chain.  When this happens, the rule is that the
augment function should return @C { true } immediately.  It does not
matter whether any entity is marked unvisited or not before exit.
# decided that there is no point in trying more repairs for the current
# defect.  Its reason for this is not the business of the augment
# function; it must return @C { true } immediately, without trying any
# more repairs (it is an error not to do this).  In that case it does
# not matter whether the entity is marked unvisited or not before exit.
@PP
However, there is an exception to this rule.  We illustrate this by
the following example.
@PP
Suppose that, in order to encourage ejection chains to remove a cluster
busy times defect, some days when the resource will be busy are chosen,
all meets assigned the resource outside those days are unassigned, and
repairs are tried which move those meets to the chosen days.
@PP
While the repairs are underway, it is desired to limit the domains of
the resource's meets to the chosen days, to keep the repairs on track.
So the repair altogether consists of unassigning some of the resource's
meets and adding a meet bound to each of the resource's meets.
@PP
Whether the repair is successful or not, after it and the chains below
it are finished, the meet bound must be removed from the resource's
meets, since the domains of the meets should not be restricted
permanently.  If the repair is unsuccessful, the meet bound is removed
by the ejector as part of undoing the repair.  But if the repair is
successful there is a problem, because the repair is not undone.
So in this case, in between receiving the @C { true } result from
@C { KheEjectorRepairEnd } and returning @C { true } itself, the
augment function should remove the meet bounds that it added.
@PP
In general, this kind of cleanup should not change the cost of
the solution.  It might remove meet or task bounds, unfix meets
or tasks, and so on.  Because it is done after a successful
chain, it cannot be assume that the solution is in the same
state as when the repair began, although the user may be able
to prove that certain aspects of it cannot have changed, based
on his knowledge of what the augment functions do.  In the
example, if no repairs remove meet bounds other than those
they add themselves, then the meet bound will still be present
and it is safe to delete it.
# @PP
# @C { KheEjectorRepairEnd } returns @C { true } to tell the user's
# augment function to not generate any more repairs.  Although this
# is often because the repair just ended was successful, it is not
# so always.  So it would be a mistake to use this @C { true } result
# as the signal to do this kind of work.
# @PP
# Instead, cases like this may be handled by passing non-@C { NULL }
# values for the @C { on_success_fn } and @C { on_success_val }
# parameters of @C { KheEjectorRepairEndLong }.  If the ejector
# subsequently decides to not undo that repair, it will then call
# @ID @C {
# on_success_fn(on_success_val)
# }
# and the user can ensure that this removes meet bounds or whatever
# is wanted.
# @PP
# The call to @C { on_success_fn } should not change the cost of the
# solution; in practice it is limited to enlarging domains, unfixing,
# and so on.  It works with all kinds of repairs and options.
# # , including @C { save_and_sort }.
# It is made at a time when the associated repair
# has been done or redone and not undone, but by no means directly after
# it is done or redone, since there may be a long chain to execute after
# that before success can be established.  It is an error to assume that
# the state of the solution when @C { on_success_fn } is called is its
# state at the end of the repair.
@End @Section

@Section
    @Title { Variants of the ejection chains idea }
    @Tag { ejection.variants }
@Begin
@LP
This section presents some variants of the basic ejection chains idea.
@BeginSubSections

@SubSection
    @Title { Defect promotion }
    @Tag { ejection.promotion }
@Begin
@LP
@I { This feature has been withdrawn to streamline the implementation.
It has had some successes but it cannot be crucial, because it does
nothing when @C { start_gm } and @C { continue_gm } are the same. }
@PP
Successful chains begin by repairing a defect which is one of
@C { start_gm }'s children, and continue by repairing defects
which are children of @C { continue_gm }.  The intention is
that @C { start_gm } should monitor some region of the solution
that has only just been assigned, so that there has been no
chance yet to repair its defects, while @C { continue_gm }
monitors the entire solution so far, or the part of it that
is relevant to repairing the defects of @C { start_gm }.
These two regions may be the same, which is why @C { start_gm }
and @C { continue_gm } may be the same group monitor; but
when they are different, the difference is important, as
the following argument shows.
@PP
Suppose only @C { start_gm } is used.  Then the ejector sets
out to repair the right defects, but is unable to follow chains
of repairs into parts of the solution that have been assigned
previously.  Or suppose only @C { continue_gm } is used.  If
the children of @C { continue_gm } are a superset of the children
of @C { start_gm }, as is always the case in practice, this
does allow a full search, but at the cost of trying again to
repair many defects for which a previous repair attempt failed
(those in @C { continue_gm } which are not also in @C { start_gm }).
This can waste a lot of running time.
@PP
At this point, however, an unexpected issue enters.  Suppose
a successful chain is found which causes some child @C { d } of
@C { continue_gm } to become defective, but which nevertheless
terminates without repairing @C { d } because it improves the
overall solution cost.  Here is a new defect, a child of
@C { continue_gm } not known to have been repaired previously,
and thus worthy of being targeted for repair; but if it is not
also a child of @C { start_gm }, it won't be.
@PP
@I { Defect promotion } addresses this issue.  When an ejection
chain is declared successful, the ejector examines the defects
created by that chain's last repair that are children of
@C { continue_gm }.  These come from the trace object in the
usual way.  It makes any of these that are not children of
@C { start_gm } into children of @C { start_gm }:  they get
dynamically added to the set of defects targeted by the current
solve.  Of course, when @C { start_gm } and @C { continue_gm }
are the same, it does nothing.
@PP
Defect promotion is optional, controlled by option
@C { es_no_promote_defects }, whose default value is @C { false }.
On one run it reduced the final solution cost from 0.04571 to 0.03743,
while increasing running time from 286.84 seconds to 490.21 seconds---a
substantial amount, but nothing like what would have occurred if
@C { start_gm } had been replaced by @C { continue_gm }.
@End @SubSection

@SubSection
    @Title { Fresh visit numbers for sub-defects }
    @Tag { ejection.articulated }
@Begin
@LP
It is common for a monitor to monitor several points in the
solution.  For example, a prefer times monitor monitors several
meets, all those derived from one point of application of the
corresponding prefer times constraint (one event).  Arguably,
having one monitor for each meet would make more sense; but
there is a problem with this, at least when the cost function
is not @C { Linear }, because then there is no well-defined value
of the cost of such a monitor.  A cost is only defined after all
the deviations of the @I { sub-defects } at all the monitored
points are added up.
@PP
The usual way to repair a defective monitor which monitors several
points is to visit each point, determine whether that point is a
sub-defect, and try some repairs if so.  When the repair is of a
main loop defect (when the current length is 1), it makes sense
for the augment function to give a fresh visit number to each
sub-defect, so that the repair at each sub-defect is free to
search the whole solution, as in this template:
@ID @C {
for( i = 0;  i < KheMonitorPointCount(m);  i++ )
{
  p = KheMonitorPoint(m, i);
  if( KheMonitorPointIsDefective(p) )
  {
    if( KheEjectorCurrLength(ej) == 1 )
      KheSolnNewGlobalVisit(soln);
    if( KheMonitorPointTryRepairs(p) )
      return;
  }
}
}
Calling @C { KheSolnNewGlobalVisit } opens up the whole solution for
visiting.  This is what would happen if the monitor was broken into
smaller monitors, one for each point.  It is important, however, not
to call @C { KheSolnNewGlobalVisit } at deeper levels, since that
amounts to allowing revisiting, so it leads to exponential time
searches.
@PP
Fresh visit numbers are @I not assigned in this way within the
augment functions supplied with KHE.  Instead, a more radical
version of the idea is offered by the @C { es_fresh_visits }
option.  When set to @C { true }, it causes
@ID @C {
if( KheEjectorCurrLength(ej) == 1 )
  KheSolnNewGlobalVisit(soln);
}
to be executed within each call to @C { KheEjectorRepairBegin },
opening up the entire solution, not just to each sub-defect at
length 1, but to each repair of each sub-defect at length 1.
@End @SubSection

#@SubSection
#    @Title { Recent failure suppression }
#    @Tag { ejection.suppression }
#@Begin
#@LP
#When the repair of a defect fails, trying to repair it again soon
#is likely to be a waste of time.  To discourage it, the following
#plan is followed when the @C { suppress_recent } option is
#@C { true }.  When it is @C { false }, the plan is still followed
#except that monitor visit numbers are never changed, reducing this
#description to the usual one.
#@PP
#A @I { phase } begins by copying @C { start_gm }'s defects
#and sorting them.  It then proceeds to try to repair each
#defect in order, stopping when it reaches the end of the
#list, unless it ends earlier as explained below.  The
#ejection chain algorithm repeatedly executes one phase,
#stopping after the first phase which repairs nothing.
#@PP
#At the start of the phase, the value of the global visit number is
#recorded; call it the @I { phase visit number }.  After each monitor
#@C { m } is repaired, if the repair failed then @C { m }'s visit
#number is set to the phase visit number.  So monitors that failed
#to repair recently have higher visit numbers.
#@PP
#When sorting at the beginning of the phase, the sort key is
#first increasing visit number, second decreasing cost, and
#third (to break ties consistently) monitor index number.
#This puts defects whose repairs failed recently near the
#back of the list.
#@PP
#When traversing the list repairing defects, if there has been
#at least one success during this phase and the next defect has a
#larger visit number than the success, the phase ends early.  This
#does not disrupt the final phase (the one which repairs nothing
#and terminates the solve), but it does avoid repairing recently
#failed defects while progress is being made elsewhere.
#@End @SubSection

@SubSection
    @Title { Ejection trees }
    @Tag { ejection.trees }
@Begin
@LP
@I {
The variant described here has been withdrawn.  Ejection beams
(Section {@NumberOf ejection.beams}) perform a similar function
more simply.
}
@PP
An @I { ejection tree } is like an ejection chain except that
at each level below the first, instead of repairing one newly
introduced defect, it tries to repair several (or all) of the
newly introduced defects, producing a tree of repairs rather
than a chain.
@PP
Ejection trees are not likely to be useful often.  It is true that
the run time of an ejection tree is limited as usual by the size of
the solution, but its chance of success is lower than usual, because
it must repair several defects at the lower level to succeed at the
higher level.  If repairing the first defect produces two new
defects, repairing each of those produces two more, and so on, then
the result is a huge number of defects that must all be repaired
successfully.  And to make a repair which introduces a defect and
then repair that defect using an ejection tree is to spend a lot
of time on a defect that can be removed much more easily by undoing
the initial repair.
@PP
However, when the original solution has a very awkward defect, the
best option may be a complex repair which usually introduces several
new defects.  For example, the best way to repair a cluster busy
times overload defect may be to unassign every meet on one of the
problem days.  In that case, it makes sense to use an ejection tree
at that level alone:  that is, to try a repair that introduces
several defects, then try to repair them by finding an ejection
chain for each.
@PP
The @C { max_sub_chains } parameter of @C { KheEjectorRepairEndLong }
allows for ejection trees, by specifying the maximum number of defects
introduced by that repair that are to be repaired.  Different repairs
may have different values of @C { max_sub_chains }.  For example,
the complex cluster busy times repair could be tried only when
@C { KheEjectorCurrLength(ej) } is 1, with @C { max_sub_chains }
set to @C { INT_MAX }.  All other repairs could be given value
1 for @C { max_sub_chains }, producing ordinary chains elsewhere.
@PP
A set of defects now has to be repaired, not necessarily just one.
One option would have been to change the interface of @C { Augment }
to pass this set to the user.  This was not done, because it would
be a major change from the targeted repairs used by ejection chains.
Instead, just as the framework handles the dynamic dispatch by defect
type, so it also accepts a whole set of defects for repair and passes
them one by one to conventional @C { Augment } calls.
@PP
The remainder of this section explains the implementation of
ejection trees (and indeed ejection chains) by presenting a
more detailed description of the @C { Augment } function than
the one given at the start of this chapter.
@PP
To begin with, it was stated earlier that the main loop tries an
augment for every defective child of @C { start_gm }.  In fact,
main loop augments are tried only for defects @C { d } such that
@ID @C {
Potential(d) = KheMonitorCost(d) - KheMonitorLowerBound(d)
}
is positive.  Clearly, when @C { Potential(d) == 0 } there is
no chance of improvement.
@PP
We also need to consider monitor cost limits, which require that the
solution not change so as to cause the cost of some given monitors
to exceed given limits (Section {@NumberOf ejection.solving}).  To
handle them, the interface of @C { Augment } is changed to
@ID @C {
bool Augment(Solution s, Cost c, Limits x, Defect d);
}
where @C { x } is a set of monitor cost limits.  @C { Augment }
returns @C { true } if the value of @C { s } afterwards is such
that @C { s }'s cost is less than @C { c } and the limits @C { x }
are all satisfied.  This condition is evaluated by
@ID @C {
bool Success(Solution s, Cost c, Limits x)
{
  return cost(s) < c && LimitsAllSatisfied(s, x);
}
}
The precondition of @C { Augment(s, c, x, d) } is changed to
@ID @C {
!Success(s, c, x) && cost(s) - GPotential(d) < c
}
That is, the solution must not have already reached the target
cost and limits, and the @I { generalized potential } of @C { d },
@C { GPotential(d) }, is large enough to suggest that repairing
@C { d } might get it there.  Its postcondition is
@C { Success(s, c, x) } if @C { true } is returned, and `@C { s }
is unchanged' otherwise.
@PP
For a main loop defect, @C { cost(s) } is @C { c },
@C { GPotential(d) } is @C { Potential(d) }, and
@C { Augment } may be called exactly when it @I is called---when
@C {  Potential(d) > 0 }.  For defects at lower levels,
@C { GPotential(d) } is the amount that the cost of
@C { d } increased when the repair that produced @C { d }
occurred, as returned by @C { KheTraceMonitorCostIncrease }
(Section {@NumberOf monitoring.group.tracing}).  Often, the
cost beforehand will be @C { KheMonitorLowerBound(d) }, so
that this increase will just be @C { Potential(d) } as before;
but if @C { d } was already defective beforehand it will
be smaller, making @C { d } less likely to be augmented.  The
point is that we can only realistically hope to remove the
new cost added when the previous repair was made, not the
pre-existing cost.  (KHE used @C { Potential(d) } here for
many years; the switch to @C { GPotential(d) }
produced better cost on average, and better running time;
a few individual instances had marginally worse cost.)
@PP
The new defects chosen for repair must be @I { open defects }:
defects whose generalized potential is positive, according to
@C { KheTraceMonitorCostIncrease }.  The @C { max_sub_chains } open
defects of largest generalized potential, or all open defects if fewer
than @C { max_sub_chains } open defects are reported by the trace,
are selected.  In the code below, this selection is made by line
@ID @C {
{d1, ..., dn} = SelectOpenDefects(new_defect_set, MaxSubChains(r));
}
# where @C { 0 <= n <= MaxSubChains(r) }
This is implemented by a call to @C { KheTraceReduceByCostIncrease }
(Section {@NumberOf monitoring.group.tracing}).
@PP
Here is the more detailed implementation of @C { Augment }:
@ID -1px @Break {0.96 1.0} @Scale @C {
bool Augment(Solution s, Cost c, Limits x, Defect d)
{
  repair_set = RepairsOf(d);
  for( each repair r in repair_set )
  {
    new_defect_set = Apply(s, r);
    if( Success(s, c, x) )
      return true;
    if( NotAtLengthLimit() )
    {
      {d1, ..., dn} = SelectOpenDefects(new_defect_set, MaxSubChains(r));
      for( i = 1;  i <= n;  i++ )
      {
	sub_c = c + GPotential(d(i+1)) + ... + GPotential(dn);
	sub_x = (i < n ? {} : x);  /* empty limit set except at end */
	if( Success(s, sub_c, sub_x) )
	  continue;
	if( cost(s) - GPotential(di) >= sub_c )
	  break;
	if( !Augment(s, sub_c, sub_x, di) )
	  break;
	if( Success(s, c, x) )
	  return true;
      }
    }
    reset s to its state just before Apply(s, r);
  }
  return false;
}
}
As before, all of this except the loop that iterates over and applies
repairs is hidden in calls to @C { KheEjectorRepairBegin } and
@C { KheEjectorRepairEnd }.  It is easy to verify that this
satisfies the revised postcondition.  The reset near the end is
carried out by a call to @C { KheMarkUndo }.
@PP
After the usual test for success immediately after the repair,
if the length limit has not been reached the new code selects
@C { n } open defects for repair, then calls @C { Augment }
recursively on each in turn.  The complicating factor is the
choice of a target cost and set of limits for each recursive
call, denoted @C { sub_c } and @C { sub_x } above.  Using the
original @C { c } and @C { x }, as is done with ejection chains,
would wrongly place the entire burden of improving the solution
onto the first recursive call.
@PP
When repairing @C { d1 }, the right cost target to shoot for is
@ID @C {
sub_c = c + GPotential(d2) + ... + GPotential(dn);
}
The best that can be hoped for from repairing @C { d2 } is
@C { GPotential(d2) }, the best that can be hoped for from
repairing @C { d3 } is @C { GPotential(d3) }, etc.  So if the
first recursive @C { Augment } cannot reduce @C { cost(s) } below
the given value of @C { sub_c }, there is little hope that after all
the recursive augments it will be reduced below @C { c }.  The
same idea is applied for each of the @C { di }.
@PP
When a recursive call to @C { Augment } changes the solution, some
@C { GPotential(di) } values may change.  So this code re-evaluates
@C { sub_c } from scratch on each iteration of the inner loop,
rather than attempting to save time by adjusting the previous
value of @C { sub_c }.
@PP
The choice of @C { sub_x } causes limits to be ignored except when
carrying out the last augment.  This is in accord with the intention
of monitor cost limits, which is to only check them at the end.  It
would be a mistake to check them earlier.  For example, the repair of
the cluster busy times defects described above is likely to violate
a monitor cost limit when it deassigns meets.  These do need to be
reassigned by the end, but they will not all be reassigned earlier.
@PP
After defining @C { sub_c } and @C { sub_x } but before the call
to @C { Augment }, the code executes
@ID @C {
if( Success(s, sub_c, sub_x) )
  continue;
if( cost(s) - GPotential(di) >= sub_c )
  break;
}
These lines ensure that the precondition of the recursive
@C { Augment } call holds at the time it is made.  If
@C { Success(s, sub_c, sub_x) } holds, then the aim of that call has
already been achieved, so the algorithm moves on to the next one.  It
does not matter that it skips the @C { Success(s, c, x) } test further
on, because there has been such a test since the last time the solution
changed.  If @C { cost(s) - GPotential(di) >= sub_c } holds, then the
algorithm has no real hope of beating @C { sub_c } by repairing @C { di },
and so no real hope of success at all, so it abandons the current repair.
@PP
@C { Success(s, c, x) } implies @C { Success(s, sub_c, sub_x) }
throughout @C { Augment }, because @C {  sub_c >= c } and @C { sub_x }
is a subset of @C { x }.  This cannot be used to simplify @C { Augment }, 
but it does have one or two interesting consequences.  For example, it
applies transitively down through all active calls to @C { Augment }, so
while @C { Success(s, sub_c, sub_x) } is @C { false } at any level of
recursion, the original aim of the ejection tree cannot be satisfied.
@PP
When repairing @C { dn }, @C { sub_c == c } and @C { sub_x == x }.
This gives confidence that @C { Augment } could succeed, and shows
that it reduces to the original @C { Augment } when
@C { MaxSubChains(r) == 1 }, except for the different expression of
how one open defect is selected.
@PP
The method described here finds the first chain that repairs
@C { d1 }, fixes it, and moves on to @C { d2 }.  Representing
the higher path by a solid arrow, the chains (successful or not) that
repair @C { d1 } by dashed arrows, and the chains (successful or
not) that repair @C { d2 } by dotted arrows, the picture is
@CD @Diag
{
AA:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 0.5c @High {} &
BB:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 0.5c @High {}
//
CC:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 1c @High {} &
DD:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 1c @High {}
//
@Arrow from { AA@NE } to { AA@SE }
@Line from { CC@N } to { DD@N }
@Arrow pathstyle { dashed } from { CC@N } to { CC@SW ** 0.25 ++ CC@SE ** 0.75 }
@Arrow pathstyle { dashed } from { CC@N } to { CC@SW ** 0.75 ++ CC@SE ** 0.25 }
@Arrow pathstyle { dotted } from { DD@N } to { DD@SW ** 0.25 ++ DD@SE ** 0.75 }
@Arrow pathstyle { dotted } from { DD@N } to { DD@SW ** 0.75 ++ DD@SE ** 0.25 }
}
Another possibility is to find the first chain that repairs @C { d1 },
then try to find chains for @C { d2 }, but if that fails, to continue
searching for other chains for @C { d1 }:
@CD @Diag
{
AA:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 0.5c @High {} &
BB:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 0.5c @High {}
//
CC:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 1c @High {} &
DD:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 1c @High {}
//
EE:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 1c @High {} &
FF:: @Box margin { 0i } outlinestyle { noline } 1c @Wide 1c @High {}
//
@Arrow from { AA@NE } to { AA@SE }
@Arrow pathstyle { dashed } from { CC@NE } to { CC@S}
@Arrow pathstyle { dashed } from { DD@NW } to { DD@S}
@Arrow pathstyle { dotted } from { EE@N } to { EE@SW ** 0.25 ++ EE@SE ** 0.75 }
@Arrow pathstyle { dotted } from { EE@N } to { EE@SW ** 0.75 ++ EE@SE ** 0.25 }
@Arrow pathstyle { dotted } from { FF@N } to { FF@SW ** 0.25 ++ FF@SE ** 0.75 }
@Arrow pathstyle { dotted } from { FF@N } to { FF@SW ** 0.75 ++ FF@SE ** 0.25 }
}
This approach is implementable within the current framework, but it has
not been tried.  Ejection beams (Section {@NumberOf ejection.beams}) do
something of this kind.
# There is no reason to think that it would be better.
@End @SubSection

#@SubSection
#    @Title { Ejection trees (old) }
#    @Tag { ejection.trees_old }
#@Begin
#@LP
#@I { This sections needs a rethink, and its implementation needs an audit }
#@LP
#An @I { ejection tree } is like an ejection chain except that
#at each level, instead of repairing just one new defect, it
#tries to repair all the new defects introduced by one repair.
#Ejectors support this via the @C { sub_chains } parameter
#of @C { KheEjectorRepairEnd }.  If this parameter is set to
#@C { true }, then for each new defect introduced by the repair
#just ended, an ejection chain called a @I { sub-chain } is
#begun.  After each sub-chain returns, if the solution cost is
#less than at the start of the original chain and limit monitors
#are satisfied, success is declared.  Otherwise, failure is
#declared after all sub-chains have returned.
#@PP
#The run time of an ejection tree is limited as usual by the size of
#the solution, but an ejection tree is more likely to actually visit
#the entire solution than an ejection chain is.  Its chance of success
#is low, because it must repair several defects at the lower level to
#succeed at the higher level.  So ejection trees are not likely to be
#useful in general.  For example, to make a repair which introduces a
#defect and then repair that defect using an ejection tree is to spend
#a lot of time repairing a defect that can be removed much more easily
#by undoing the initial repair.
#@PP
#However, when the original solution has a defect which cannot be
#repaired simply, the best option may be a complex repair which is
#virtually guaranteed to introduce several new defects.  In that
#case, it makes sense to try the ejection tree style of repair at
#that level alone:  that is, to try a repair that introduces several
#defects, then try to repair those defects by finding a sub-chain
#for each.  This is quite easy to arrange:  the complex repair is
#tried only when @C { KheEjectorCurrLength } is 1, and its
#@C { sub_chains } parameter is the only one set to @C { true }.
#The ejector accepts an arbitrary mix of @C { sub_chains }
#repairs with ordinary ones.
#@PP
#For the overall repair to succeed, in general several sub-chains need
#to contribute some cost reduction, but there is no way to know how
#much to demand from each chain; so the ejector tells each sub-chain
#that its goal is to reduce the cost of the solution to below its value
#at the start of the sub-chain, not to below its value at the start of
#the original chain.  It also tells sub-chains that they have no limit
#monitors to satisfy, since checking them does not make sense until
#after all sub-chains have run.  In all other respects, including length,
#a sub-chain is treated like a continuation of the original chain.
#@End @SubSection

@SubSection
    @Title { Sorting repairs }
    @Tag { ejection.sortrepairs }
@Begin
@LP
@I {
The variant described here never performed very well, and it has now
been withdrawn.
}
@PP
Each repair is usually followed immediately by recursive calls which
extend the chain, where applicable.  Setting the @C { save_and_sort }
parameter of @C { KheEjectorRepairEndLong } to @C { true } invokes a
different arrangement.  Paths representing the repairs are saved in
the ejector without recursion.  After the last repair they are sorted
into increasing order of the cost of the solutions they produce, and
each is tried in turn, just as though they had occurred in that sorted
order @Cite { $haan2007 }.
@PP
In practice, @C { save_and_sort } would be given the same value
for every repair of a given defect.  However, it is legal to use a
mixture of values.  Those given value @C { true } will be saved,
those given value @C { false } will be recursed on immediately in
the usual way.  If any of those lead to success, that chain is
accepted and any saved repairs are forgotten.
@PP
Only repairs with some hope of success are saved:  those for which
@ID @C {
Success(s, c, x) || (NotAtLengthLimit() &&
  cost(s) - (GPotential(d1) + ... + GPotential(dn)) < c)
}
holds after the repair, in the terminology of
Section {@NumberOf ejection.trees}.
@PP
The author's experience with @C { save_and_sort } has been
disappointing.  Chains can end successfully anywhere in the search
tree, and low solution cost at an intermediate point is not a
good predictor of a successful end.  Every saved repair is executed
once before sorting to establish the solution cost after it, then
undone.  If the repair is tried later, it is executed again
(by a path redo).  The significant benefit needed to justify
this extra work does not seem to be there.
@End @SubSection

#@SubSection
#    @Title { Adjustment on success }
#    @Tag { ejection.adjustment }
#@Begin
#@LP
#Suppose that, in order to encourage ejection chains to remove a cluster
#busy times defect, some days when the resource will be busy are chosen,
#all meets assigned the resource outside those days are unassigned, and
#repairs are tried which move those meets to the chosen days.
#@PP
#While the repairs are underway, it is desired to limit the domains of
#the resource's meets to the chosen days, to keep the repairs on track.
#So the repair altogether consists of unassigning some of the resource's
#meets and adding a meet bound to each of the resource's meets.
#@PP
#Whether the repair is successful or not, after it and the chains below
#it are finished, the meet bound must be removed, since the domains of
#the meets are not supposed to be restricted permanently.  If the
#repair is unsuccessful, the meet bound is removed by the ejector as
#part of undoing the repair.  But if the repair is successful there
#is a problem, because it is not undone.
#@PP
#@C { KheEjectorRepairEnd } returns @C { true } to tell the user's
#augment function to not generate any more repairs.  Although this
#is often because the repair just ended was successful, it is not
#so always.  So it would be a mistake to use this @C { true } result
#as the signal to do this kind of work.
#@PP
#Instead, cases like this may be handled by passing non-@C { NULL }
#values for the @C { on_success_fn } and @C { on_success_val }
#parameters of @C { KheEjectorRepairEndLong }.  If the ejector
#subsequently decides to not undo that repair, it will then call
#@ID @C {
#on_success_fn(on_success_val)
#}
#and the user can ensure that this removes meet bounds or whatever
#is wanted.
#@PP
#The call to @C { on_success_fn } should not change the cost of the
#solution; in practice it is limited to enlarging domains, unfixing,
#and so on.  It works with all kinds of repairs and options.
## , including @C { save_and_sort }.
#It is made at a time when the associated repair
#has been done or redone and not undone, but by no means directly after
#it is done or redone, since there may be a long chain to execute after
#that before success can be established.  It is an error to assume that
#the state of the solution when @C { on_success_fn } is called is its
#state at the end of the repair.
#@End @SubSection

@SubSection
    @Title { Ejection beams }
    @Tag { ejection.beams }
@Begin
@LP
@I { Ejection beams } are yet another variant of the basic ejection
chains idea.  They are similar to the now-withdrawn ejection trees
in that they aim to allow the algorithm to carry on when more than
one defect is introduced by a repair.
# ; but they are arguably easier to deal with.
@PP
An @I { ejection beam }, or just @I { beam }, is a non-empty set of
monitors @C { m }, each with its @I { cost }, denoted @C { cost(m) },
and its @I { target }, denoted @C { targ(m) }.  The value of
@C { cost(m) } is the cost of @C { m } in the current solution, as
usual.  The value of @C { targ(m) } will be specified shortly; it is
a value that the algorithm aspires to reduce @C { cost(m) } to.
@PP
A beam is a true set:  no monitor may appear in it twice.  Its
cardinality (its number of monitors) may be at most some small
integer @C { K } (a parameter of the algorithm) whose value must
be at least 1.  The value might be 2 or 3, for example.
@PP
Instead of passing a single defective monitor, the
basic call passes an entire beam @C { B }:
@ID @C {
bool Augment(Solution s, Cost c, Beam B)
}
As usual, if @C { Augment } can find a way to reduce the cost of @C { s }
to less than @C { c }, it does so and returns @C { true }, otherwise
it changes nothing and returns @C { false }.  But now it has a set of
monitors, all those in @C { B }, to repair.  When @C { Augment } is
called, each of these monitors @C { m } is unvisited and satisfies
@ID @C {
cost(m) > targ(m) >= 0
}
Furthermore, along with the usual @C { cost(s) >= c }, the condition
@ID @C {
Open(s, c, B) = cost(s) - sum[m in B](cost(m) - targ(m)) < c
}
holds initially.  So one way for @C { Augment } to succeed would be
to reduce the cost of every monitor in @C { B } down to its target
without introducing any new defects.
@PP
Like the ejection chain algorithm, the ejection beam algorithm has a
main loop that tries to repair each top-level defect @C { m } whose
cost exceeds its lower bound.  To do this it first marks all monitors
unvisited, then it calls @C { Augment }, setting @C { s } to the
current solution, @C { c } to the cost of the current solution,
and @C { B } to a beam consisting of a single element, @C { m },
setting @C { targ(m) } to @C { m }'s lower bound.  This clearly
satisfies the initial conditions of @C { Augment }.
@PP
Here is the implementation of @C { Augment }.  We've added a
@C { Limits } parameter which handles the limits imposed by
@C { KheEjectorAddMonitorCostLimit } (Section {@NumberOf ejection.solving}):
@ID -1px @Break {0.96 1.0} @Scale @C {
bool Augment(Solution s, Cost c, Limits x, Beam B)
{
  choose an arbitrary element d of B;
  MonitorSetVisited(d);
  repair_set = RepairsOf(d);
  for( each repair r in repair_set )
  {
    new_defect_set = Apply(s, r);
    if( Success(s, c, x) )
      return true;
    if( LimitsNotReached() && BeamMerge(s, c, B, new_defect_set, K, &B2) )
    {
      if( Augment(s, c, x, B2) )
	return true;
    }
    reset s to its state just before Apply(s, r);
  }
  return false;
}
}
Here @C { Success } compares the solution cost with @C { c } and
checks the limits:
@ID @C {
bool Success(Solution s, Cost c, Limits x)
{
  return cost(s) < c && LimitsAllSatisfied(s, x);
}
}
while @C { LimitsNotReached } returns @C { true } if the various
limits, on number of augments and so on, have not yet been reached.
@PP
Apart from a few minor details, this version of @C { Augment } is
clearly the original one with a set of monitors @C { B } instead
of a single defect @C { d }.  It demands nothing additional
from the user except a single value for @C { K } at the start.
@PP
The key new step is @C { BeamMerge }.  It returns a new non-empty beam
@C { B2 } which is the set union of @C { B } and @C { new_defect_set },
sorted into decreasing @C { cost(m) - targ(m) } order.  Monitors are
omitted when they are visited, and they are dropped from the end of
the sorted list until just before dropping another would make
@C { Open(s, c, B2) } @C { false }.  If the number of monitors
remaining exceeds @C { K }, @C { MakeBeam } returns @C { false }.  So
if the recursive call to @C { Augment } is made, @C { B2 } is a beam of
size at most @C { K } which satisfies the precondition of @C { Augment }.
@PP
A new monitor @C { m } entering @C { B2 } has its @C { targ(m) }
value set to its cost before @C { r } was applied.  Clearly,
@C { cost(m) > targ(m) >= 0 }, because @C { targ(m) } is
@C { cost(m) } before @C { r } was applied, and it was @C { r }
that caused @C { m } to appear in @C { new_defect_set }, meaning
that @C { m }'s cost increased.  The algorithm aspires to
remove the new defects introduced by @C { r }, which in the
case of @C { m } means returning its cost to what it was before
@C { r }, so this is a suitable value for @C { targ(m) }.
@PP
A monitor @C { m } could lie in both @C { B } and @C { new_defect_set },
if it was a defect before applying @C { r }, and @C { r } made it
worse.  That is fine; it will appear only once in @C { B2 }, with
its original @C { targ(m) }.
@PP
Ejectors always run the ejection beam code, but the default value of
@C { K } is 1, producing ejection chains.  @C { K } is determined by
the { @F es_max_beam } option (Section {@NumberOf ejection.repair}), so
setting this to a value larger than 1 is the way to get true ejection beams.
@End @SubSection

#@SubSection
#    @Title { Beyond the first success }
#    @Tag { ejection.extended }
#@Begin
#@LP
#@I { Currently not implemented; the options are accepted but not acted on. }
#@LP
#@C { KHE_EJECTOR_FIRST },
#the usual value of @C { KheEjectorMinorScheduleAdd }'s @C { solve_type }
#parameter, causes the first chain that reduces
#the cost of the solution to be accepted.  Most of the discussion in
#previous sections has assumed this type of solve.  This section presents
#the other two values of this parameter, @C { KHE_EJECTOR_BEST_COST } and
#@C { KHE_EJECTOR_BEST_DISRUPTION }.
#@PP
#@C { KHE_EJECTOR_BEST_COST } causes the ejector to keep searching
#until its search space is exhausted, and then apply the chain that
#reduced the cost of the solution the most, if any.  For example, when
#@C { max_length } is 1 the result is a steepest-descent hill-climber.
#@PP
#@C { KHE_EJECTOR_BEST_DISRUPTION } is similar, searching
#for a lexicographically minimum value of the pair
#@ID @C { (disruption, cost) }
#That is, it tries to find a cost-reducing chain of minimum disruption.
#In this case, since disruptions are non-negative, whenever the total
#disruption of the current chain exceeds the disruption of the best
#chain so far (if any), the current chain is terminated.
#@PP
#No changes are needed to augment functions when using these variants;
#the framework takes care of all the details.
## @C { KheEjectorRepairEnd } always returns @C { false } when these
## variants are in use, inducing the augment functions to keep searching.
## @PP
## The current chain and the best chain so far (if any) are stored in
## transactions, so the repairs must be based on operations that
## transactions store.  Care must be taken not to implement augment
## functions in a way that causes these transactions to grow to
## enormous length.  In practice, this means that the basic elements
## of repairs must be undone, step by step, in the reverse order to
## how they were done, so that the optimizations which cancel operations
## within transactions in these cases can take effect.  For simple
## assignments and deassignments this is trivial, but for larger
## repairs, such as swaps and Kempe time moves, the only safe
## method is to enclose the repair in a transaction and remove the
## repair by undoing the transaction.  KHE's functions do this.
#@PP
#The running time of each augment here is similar to that of an
#unsuccessful augment using first success.  Since many augments
#are unsuccessful, in theory these variants should not run much
#more slowly than first success.  In practice, however, the author
#has observed them running significantly more slowly, although
#they cannot and do not run indefinitely.
#@End @SubSection

@EndSubSections
@End @Section

@Section
    @Title { Gathering statistics }
    @Tag { ejection.statistics }
@Begin
@LP
Ejectors gather statistics about their performance.  This takes a negligible
amount of time, as the author has verified by comparing run times with
preprocessor flag @C { KHE_EJECTOR_WITH_STATS } in the ejector source
file set to 0 (no statistics) and 1 (all statistics).  On two typical
instances, the increase in overall run time caused by gathering
statistics was less than 0.1 seconds.
@BeginSubSections

@SubSection
    @Title { Options for choosing ejectors and schedules }
    @Tag { ejection.statistics.options }
@Begin
@LP
Each ejector holds its own statistics, independently of other
ejectors.  Some statistics accumulate across the entire lifetime
of an ejector; they are never reset.  This makes it possible, for
example, to measure the performance of time repair ejection chains
and resource repair ejection chains over an entire set of instances,
by carrying out all time repairs in all instances using one
ejector and all resource repairs in all instances using another.
@PP
To facilitate this, options objects usually contain two ejectors,
under names @C { "ejector1" } and @C { "ejector2" }, as explained in
Section {@NumberOf ejection.repair.ejectors}; they could contain more.  
@PP
The next question is what schedules to give to these ejectors.
A set of schedules is an option, so the @C { options } object
has option @C { es_schedules } for it, whose value is a string.
Its default value is @C { "1+,u-" }, for the meaning of which see
Section {@NumberOf ejection.ejectors}.
@PP
Setting the schedule string does not set any ejector schedules,
it merely sets one option of @C { options }, to a fresh copy of
the string it is given.  User code must set the actual schedules,
using helper function @C { KheEjectorSetSchedulesFromString }
(Section {@NumberOf ejection.ejectors}).
@End @SubSection

@SubSection
    @Title { Statistics for analysing Kempe meet moves }
    @Tag { ejection.statistics.kempe }
@Begin
@LP
The ejector itself does not maintain statistics for analysing
Kempe meet moves.  These are stored in @C { kempe_stats }
objects, one of which is conveniently available from option
@C { ts_kempe_stats } (Section {@NumberOf time_solvers.kempe}).  This
object is passed to the calls to @C { KempeMeetMove } made by the
augment functions described in this chapter.  Only Kempe meet moves
which are complete repairs on their own are passed this object, not
Kempe meet moves combined with other operations (meet splits and
merges, for example).  So by the end of an ejction chain run,
statistics about these Kempe meet moves will have been accumulated
in the @C { ts_kempe_stats } option of the @C { options } object
passed to the ejection chain repairs.
@End @SubSection

@SubSection
    @Title { Statistics describing a single solve }
    @Tag { ejection.statistics.chains }
@Begin
@LP
The statistics presented in this section make sense only for one call
to @C { KheEjectorSolveEnd }.  So they are available only until the
next call to @C { KheEjectorSolveEnd }, when they are reset.
@PP
An @I { improvement } is an ejection chain or tree, rooted in a defect
examined by the main loop, which is applied to the solution and reduces
its cost.  Each time an improvement is applied, four facts about it
are recorded.  The number of improvements applied is returned by
@ID @C {
int KheEjectorImprovementCount(KHE_EJECTOR ej);
}
and the four facts about the @C { i }th improvement (counting from
0 as usual) are returned by
@ID @C {
int KheEjectorImprovementNumberOfRepairs(KHE_EJECTOR ej, int i);
float KheEjectorImprovementTime(KHE_EJECTOR ej, int i);
KHE_COST KheEjectorImprovementCost(KHE_EJECTOR ej, int i);
int KheEjectorImprovementDefects(KHE_EJECTOR ej, int i);
}
These return the number of repairs in the @C { i }th improvement
(this tends to increase with @C { i }), the time from the moment
when @C { KheEjectorSolveEnd } was called to the moment after the
improvement was applied, the solution cost afterwards, and the
number of defects of @C { start_gm } afterwards.  Times are measured in
seconds, to a precision much better than one second.  There are also
@ID @C {
KHE_COST KheEjectorInitCost(KHE_EJECTOR ej);
int KheEjectorInitDefects(KHE_EJECTOR ej);
}
which return the cost and number of defects when @C { KheEjectorSolve }
began.
@End @SubSection

@SubSection
    @Title { Statistics describing multiple solves }
    @Tag { ejection.statistics.augment_and_repair }
@Begin
@LP
The statistics presented in this section make sense across
multiple calls to @C { KheEjectorSolveEnd }.  They are initialized
when the ejector is created and never reset.
@PP
It is interesting to see how many repairs make up one improvement.
Each time an improvement occurs on any solve during the lifetime
of the ejector, one entry in a histogram of numbers of repairs is
incremented.  This histogram can be accessed at any time by calling
@ID @C {
int KheEjectorImprovementRepairHistoMax(KHE_EJECTOR ej);
int KheEjectorImprovementRepairHistoFrequency(KHE_EJECTOR ej,
  int repair_count);
}
@C { KheEjectorImprovementRepairHistoMax } returns the maximum,
over all improvements @M { x }, of the number of repairs that
make up @M { x }, or 0 if there have been no improvements.
@C { KheEjectorImprovementRepairHistoFrequency } returns the number
of improvements with the given number of repairs.  Also, functions
@ID @C {
int KheEjectorImprovementRepairHistoTotal(KHE_EJECTOR ej);
float KheEjectorImprovementRepairHistoAverage(KHE_EJECTOR ej);
}
use this same basic information to find the total number of
improvements, and the average number of repairs per improvement
when there is at least one improvement.
@PP
Another histogram, again with one element for each improvement,
records the number of calls to @C { Augment } since the most
recent on in the main loop:
@ID @C {
int KheEjectorImprovementAugmentHistoMax(KHE_EJECTOR ej);
int KheEjectorImprovementAugmentHistoFrequency(KHE_EJECTOR ej,
  int augment_count);
int KheEjectorImprovementAugmentHistoTotal(KHE_EJECTOR ej);
float KheEjectorImprovementAugmentHistoAverage(KHE_EJECTOR ej);
}
This is helpful, for example, in deciding whether it would be
useful to terminate a search after some number of augments has
failed to find an improvement.  A method of doing this is built
into ejectors, but not offered as an official option at the moment.
@PP
Another interesting question is how successful the various augment
functions and repairs are.  There are methodological issues here,
however.  For example, if one kind of repair is tried before another,
it has more opportunities to both succeed and fail than the other.
If there are several alternatives to choose from, the best test
would be to compare the results of several complete runs, one for
each alternative.  No statistical support is needed for that.  But
even after the best alternatives are chosen, there remains the
question of whether each component is pulling its weight.  The
statistics to be described now attempt to answer this question.
@PP
An @I { augment type } is a small integer representing one kind
of augment function.  A @I { repair type } is a small integer
representing one kind of repair.  Functions @C { KheEjectorAddAugment }
and @C { KheEjectorAddGroupAugment } assign an augment type to each
augment function, and thus to each call on an augment function.
Each repair is followed by a call to @C { KheEjectorRepairEnd }
(Section {@NumberOf ejection.augment}), and its @C { repair_type }
parameter assigns a repair type to that repair.  Based on this
information, the ejector records the following statistics:
@NumberedList

@LI @OneRow {
For each distinct @C { augment_type }, the number of repairs
made by calls on augment functions with that augment type;
}

@LI @OneRow {
For each distinct @C { (augment_type, repair_type) } pair, the
number of repairs of that repair type made by calls on augment
functions with that augment type;
}

@LI @OneRow {
For each distinct @C { augment_type }, the number of successful
repairs made by calls on augment functions with that augment type;
}

@LI @OneRow {
For each distinct @C { (augment_type, repair_type) } pair, the
number of successful repairs of that repair type made by calls
on augment functions with that augment type.
}

@EndList
Only repairs with a @C { true } value for the @C { success }
parameter of @C { KheEjectorRepairEndLong } are counted.
# When the @C { save_and_sort } option is in use, not all saved
# repairs are counted, only those redone after sorting.
For the
purposes of statistics gathering, a repair is considered
successful if it causes its enclosing @C { Augment } function to
return @C { true }, whether this happens immediately, or after
recursion, or after saving and sorting.  The statistics may be
retrieved at any time by calling
@ID @C {
int KheEjectorTotalRepairs(KHE_EJECTOR ej, int augment_type);
int KheEjectorTotalRepairsByType(KHE_EJECTOR ej, int augment_type,
  int repair_type);
int KheEjectorSuccessfulRepairs(KHE_EJECTOR ej, int augment_type);
int KheEjectorSuccessfulRepairsByType(KHE_EJECTOR ej, int augment_type,
  int repair_type);
}
where @C { augment_type } and @C { repair_type } are arbitrary
non-negative integers.  Based on these numbers, a reasonable
analysis of the effectiveness of the augment functions and their
repairs can be made.  For example, the effectiveness of an augment
function can be measured by the ratio of the third number to the
first.  Adding up the result of @C { KheEjectorTotalRepairsByType }
over all values of @C { repair_type } produces the result of
@C { KheEjectorTotalRepairs }, and adding up the result of
@C { KheEjectorSuccessfulRepairsByType } over all values of
@C { repair_type } produces the result of @C { KheEjectorSuccessfulRepairs }.
@End @SubSection

@SubSection
    @Title { Organizing augment and repair types }
    @Tag { ejection.statistics.labels }
@Begin
@LP
@C { KheEjectorAddAugment } and @C { KheEjectorAddGroupAugment }
accept any @C { augment_type } values.  The user should define
these values using an enumerated type.  The following function
may be called any number of times during the ejector's setup phase,
to tell it what values to expect:
@ID @C {
void KheEjectorAddAugmentType(KHE_EJECTOR ej, int augment_type,
  char *augment_label);
}
This tells @C { ej } to expect calls to @C { KheEjectorAddAugment }
and @C { KheEjectorAddGroupAugment } with the given value of
@C { augment_type }, and associates a label with that augment type.
Labels must be non-@C { NULL }; copies are stored, not originals.  No
checks are made that the values passed via @C { KheEjectorAddAugment }
and @C { KheEjectorAddGroupAugment } match those declared by
@C { KheEjectorAddAugmentType }.  But if they do, then making tables
of statistics is simplified by calling the following functions afterwards.
@PP
To visit all the augment types declared by calls to
@C { KheEjectorAddAugmentType }, call
@ID @C {
int KheEjectorAugmentTypeCount(KHE_EJECTOR ej);
int KheEjectorAugmentType(KHE_EJECTOR ej, int i);
}
To retrieve the label corresponding to an augment type, call
@ID @C {
char *KheEjectorAugmentTypeLabel(KHE_EJECTOR ej, int augment_type);
}
In this way, suitable values for passing to @C { KheEjectorTotalRepairs }
and the other statistics functions above can be generated, along with
labels to identify the statistics.
@PP
The same functionality is offered for repair types.
@C { KheEjectorRepairBegin } may be passed any values for @C { repair_type },
but the user knows which values will be passed, and the following function
may be called any number of times during the ejector's setup phase to
tell it this:
@ID @C {
void KheEjectorAddRepairType(KHE_EJECTOR ej, int repair_type,
  char *repair_label);
}
@C { KheEjectorAddRepairType } declares that @C { ej } can expect calls
to @C { KheEjectorRepairBegin } with the given value of @C { repair_type },
and associates a label with that repair type.  Labels must be
non-@C { NULL }; copies are stored, not originals.  No checks are made
that the values passed via @C { KheEjectorRepairBegin } match those
declared by @C { KheEjectorAddRepairType }.  But if they do, then
making tables of statistics is simplified by calling the following
functions afterwards.
@PP
To visit all the repair types declared by calls to
@C { KheEjectorAddRepairType }, call
@ID @C {
int KheEjectorRepairTypeCount(KHE_EJECTOR ej);
int KheEjectorRepairType(KHE_EJECTOR ej, int i);
}
To retrieve the label corresponding to a repair type, call
@ID @C {
char *KheEjectorRepairTypeLabel(KHE_EJECTOR ej, int repair_type);
}
There is no way to declare which combinations of augment type and
repair type to expect.  The author handles this by ignoring cases
where @C { KheEjectorTotalRepairsByType } returns 0.
@End @SubSection

@EndSubSections
@End @Section

@Section
    @Title { Ejection chain time and resource repair functions }
    @Tag { ejection.repair }
@Begin
@LP
Previous sections have described ejectors in general.  This
section describes how ejectors are put to use in three
ejection chain time and resource repair functions:
@ID @C {
bool KheEjectionChainNodeRepairTimes(KHE_NODE parent_node,
  KHE_OPTIONS options);
bool KheEjectionChainLayerRepairTimes(KHE_LAYER layer,
  KHE_OPTIONS options);
bool KheEjectionChainRepairResources(KHE_TASKING tasking,
  KHE_OPTIONS options);
}
@C { KheEjectionChainNodeRepairTimes } repairs the assignments
of the meets of the descendants of the child nodes of @C { parent_node },
and @C { KheEjectionChainLayerRepairTimes } repairs the assignments of
the meets of the descendants of the child nodes of @C { layer }.  This
is useful for repairing the time assignments of a layer immediately
after they are made, without wasting time on earlier layers where
repairs have already been tried and are very unlikely to succeed.
@C { KheEjectionChainRepairResources } repairs the assignments of the
tasks of @C { tasking }.
@PP
All three functions make assignments as well as change them, so may
be used to construct solutions as well as repair them.  However,
there are better ways to construct solutions.
@PP
Although these functions target different parts of the solution,
they share much of their implementation.  In particular, they
call the same augment functions, although the detailed behaviour
of those functions depends on several options.
@PP
Here is the full list of options consulted or set by these functions.
By convention, these options all have names beginning with @C { es_ }.
The most important options from the point of view of the user are
those that he can reasonably set:
@TaggedList

@DTI { @F rs_ejector_off }
{
A Boolean option which, when @C { true }, causes
@C { KheEjectionChainRepairResources } to do nothing.
}

@DTI { @F { es_ejector1 }, @F { es_ejector2 } }
{
These two options hold ejector objects.  @C { KheEjectionChainNodeRepairTimes }
and @C { KheEjectionChainLayerRepairTimes } use the ejector object stored
under key @F { es_ejector1 }, while @C { KheEjectionChainRepairResources }
uses the ejector object stored under key @F { es_ejector2 }.  For full
details on this method of obtaining ejector objects, see
Section {@NumberOf ejection.repair.ejectors}.  While there is no
pressing need for the user to set these options, since they will
be set the first time they are needed, retrieving them at the end
of the solve can be useful, since they will then contain statistics
on the performance of the ejection chain algorithm.
}

@DTI { @F es_vizier_node }
{
A Boolean option which, when @C { true }, instructs
@C { KheEjectionChainNodeRepairTimes } and
@C { KheEjectionChainLayerRepairTimes } to insert a vizier node
(Section {@NumberOf time_structural.nodes.vizier}) temporarily
while they run.
}

@DTI { @F es_layer_repair_long }
{
A Boolean option which, when @C { true }, instructs
@C { KheEjectionChainLayerRepairTimes } to target every
layer up to and including the current layer when repairing the
current layer.  Otherwise only the current layer is targeted.
}

@DTI { @F es_nodes_before_meets }
{
A Boolean option which, when @C { true }, instructs augment
functions that try both node swaps and meet moves to try the
node swaps first.
}

@DTI { @F es_kempe_moves }
{
This option determines whether augment functions that move
meets use Kempe moves in addition to ejecting and basic
ones (Section {@NumberOf time_solvers.kempe}).  Its possible
values are @F { true }, meaning to use them, @C { false },
meaning to not use them, and @F { large_layers } (the default),
meaning to use them when moving the meets of nodes that lie in
layers of large duration relative to the cycle duration,
reasoning that swaps are usually needed when such meets are moved.
}

@DTI { @F es_fuzzy_moves }
{
A Boolean option which, when @C { true }, instructs augment
functions that move meets to try fuzzy meet moves
(Section {@NumberOf time_solvers.repair.meet_set}) in addition
to the other kinds of meet moves.  If they do, to conserve
running time they only do so when repairing a defect of the
current best solution, not when repairing a defect introduced
by a previous repair.  At present the @C { width }, @C { depth },
and @C { max_meets } arguments passed to @C { KheFuzzyMeetMove }
are fixed constants.
}

@DTI { @F es_no_ejecting_moves }
{
A Boolean option which, when @C { true }, instructs augment
functions that assign and move meets to not use ejecting moves,
only basic ones (Section {@NumberOf time_solvers.kempe}).
}

@DTI { @F es_no_limit_busy_sequences }
{
A Boolean option which, when @C { true }, instructs augment
functions that repair limit busy times defects to move only
single assignments, not sequences of assignments.
}

@DTI { @F es_schedules }
{
The value here is a string describing the schedules to apply
to an ejector.  The default value is @C { "1+,u-" }.  For the
meaning of this, consult Section {@NumberOf ejection.ejectors}.
}

@DTI { @F es_max_augments }
{
An upper limit on the number of augments tried on each attempt
to repair a main loop defect.  The default value is @C { 120 },
which may be more effective than a larger number, especially
when there is a time limit, because it helps the search to
not waste time on lost causes.
}

@DTI { @F es_max_repairs }
{
An upper limit on the number of repairs tried on each attempt
to repair a main loop defect.  The default value is @C { INT_MAX }.
}

@DTI { @F es_no_promote_defects }
{
(@I { withdrawn }) A Boolean option which, when @C { true }, instructs
an ejector not to promote defects (Section {@NumberOf ejection.promotion}).
}

@DTI { @F es_fresh_visits }
{
A Boolean option which, when @C { true }, instructs an ejector to
make fresh visits (Section {@NumberOf ejection.articulated}).
}

@DTI { @F es_max_beam }
{
An integer option which gives the maximum number of monitors that
may appear in a beam (Section {@NumberOf ejection.beams}).  The
default value is 1, producing ejection chains rather than beams.
}

@DTI { @F es_limit_defects }
{
An option whose value is either @C { "unlimited" } or an integer.
This integer is a limit on the number of defects handled by the
main loop of the ejector.  Each time the main list of defects
is copied and sorted, if its size exceeds this limit, defects are
dropped from the end until it doesn't.  When the option is not
set, or its value is @C { "unlimited" }, no limit is applied.
}

@DTI { @F es_task_seq_swaps_off }
{
A Boolean option which, when @C { true }, causes each move
of a sequence of tasks on adjacent days to not try to find a
second sequence of tasks in the other direction.
}

@DTI { @F es_group_limit_resources_off }
{
A Boolean option which, when @C { true }, instructs
@C { KheEjectionChainPrepareMonitors }
(Section {@NumberOf ejection.repair.primary})
to not group limit resources monitors for the same
events and resource types.  Grouping these monitors is not exact,
which is why this option is offered to turn it off, but it may be
helpful anyway, to reduce the density of these constraints.
}

@DTI { {@F es_widening_off}, {@F es_reversing_off}, {@F es_balancing_off}  }
{
Boolean options which, when @C { true }, cause widening, reversing,
and balancing to be omitted from task move repairs.
@DP
@I { Note:  Task move repairs treat tasks @C { t } for which
@C { KheTaskNeedsAssignmentHasCost(t) } is @C { false } as free
time.  Inserting this note in the right place is still to do.
}
}

@DTI { {@F es_widening_max} }
{
When widening is in effect (when @F es_widening_off is @C { false }),
this integer option determines the maximum number of frame time groups
that widening covers.  Calling this number @M { m }, and supposing
the task set being moved already covers @M { s } time groups, the
maximum is @M { max(m, s) }.  The default value is 4.
}

@DTI { {@F es_balancing_max} }
{
When balancing is in effect (when @F es_balancing_off is @C { false }),
this integer option determines the maximum number of repairs that
move a task set in the other direction to the main move.  The
default value is 12.
}

@DTI { {@F es_full_widening_on} }
{
A boolean option which, when @C { true }, includes full widening
in task move repairs.  This swaps timetables from the task being
moved back to the start or forward to the end of the cycle.  It
is effective, but its slowness typically increases cost on solves
limited by time.
}

@DTI { {@F es_optimal_on} }
{
A boolean option which, when @C { true }, causes optimal widened
task set moves to be used.  In that case, @F es_optimal_width
just below is consulted, as is {@F es_balancing_off} above, but
the other options from {@F es_widening_off} down to here are
ignored.
}

@DTI { {@F es_optimal_width} }
{
When @F es_optimal_on is @C { true }, this integer option determines
how many extra days to include in the optimal move.  The default value,
6, adds 6 extra days.  When these are added to the one day typically
involved in the original move, this covers a full week.
}

# @DTI { @F es_nocost_off } {
# Task move repairs treat tasks @C { t } for which
# @C { KheTaskNonAssignmentHasCost(t, false) }
# returns @C { false } the same as free time, unassigning them when
# necessary to avoid clashes.  This Boolean option, when @C { true },
# turns off this special behaviour for these tasks, which may save time.
# }

@EndList
The following options are set by KHE's functions, making it futile
for the user to set them:
@TaggedList

@DTI { @F es_split_moves }
{
A Boolean option which, when @C { true }, instructs augment
functions that move meets to try split meet moves in addition
to other kinds of meet moves.  @C { KheGeneralSolve2020 } sets
this option to @C { true } when the instance contains soft split
events or distribute split events constraints, reasoning that
they may not have been satisfied by structural solvers.
}

@DTI { @F es_repair_times }
{
A Boolean option which, when @C { true }, lets augment functions
change meet assignments.  @C { KheEjectionChainNodeRepairTimes } and
@C { KheEjectionChainLayerRepairTimes } set it to @C { true }, while
@C { KheEjectionChainRepairResources } sets it to @C { false }.
}

@DTI { @F es_limit_node }
{
This option holds a node object.  When it is non-@C { NULL }, it causes
augment functions that assign and move meets to limit their repairs to
the descendants of that node.  This option is set by all three functions.
}

@DTI { @F es_repair_resources }
{
A Boolean option which, when @C { true }, lets augment functions
change task assignments.  @C { KheEjectionChainNodeRepairTimes } and
@C { KheEjectionChainLayerRepairTimes } set it to @C { false }, while
@C { KheEjectionChainRepairResources } sets it to @C { true }.
}

# @DTI { @F es_use_event_timetable_monitor }
# {
# A Boolean option which, when @C { true }, instructs an ejector to
# create an event timetable monitor for the events of the instance, for
# use by its augment functions.  @C { KheEjectionChainRepairResources }
# sets this option to @C { true }.
# }

@EndList
All three functions require certain monitor groupings, but they set
them up and remove them themselves.  It is reasonable to worry about
the time it takes to set up these group monitors.  To investigate this
question, the author ran just the group monitor setup and removal
parts of function @C { KheEjectionChainNodeRepairTimes } 10000
times on a typical instance (BGHS98) and measured the time taken.
This was 31.35 seconds, or about 0.003 seconds per setup"/"remove.
This is not significant if it is done infrequently.
@BeginSubSections

@SubSection
    @Title { Obtaining ejector objects }
    @Tag { ejection.repair.ejectors }
@Begin
@LP
The first issue is where the ejector objects come from.  They
need to be loaded with the right augment functions, and they
need to be available after the solve, to give access to their
statistics.  KHE offers a function for creating an ejector
object with its augment functions loaded:
@ID {0.98 1.0} @Scale @C {
KHE_EJECTOR KheEjectionChainEjectorMake(KHE_OPTIONS options, HA_ARENA a);
}
This returns a new ejector object, ready to solve with, stored in
arena @C { a }.  Its schedules come from the @C { es_schedules }
option of @C { options }, and its augment functions are the ones
defined by KHE.  However, the three ejection chain solvers obtain
their ejector objects indirectly, by calling
@ID {0.98 1.0} @Scale @C {
KHE_EJECTOR KheEjectionChainEjectorOption(KHE_OPTIONS options, char *key);
}
This retrieves the object from @C { options } with the given key,
casts it to @C { KHE_EJECTOR }, and returns it; or if there is no
such object, it calls @C { KheEjectionChainEjectorMake(options, a) }
to create one, adds it to @C { options } under the given key, and
returns it.  The ejector's lifetime is the lifetime of @C { options },
because @C { a } is @C { KheOptionsArena(options) }.
@PP
For @C { KheEjectionChainNodeRepairTimes } and
@C { KheEjectionChainLayerRepairTimes } the key is @C { "es_ejector1" };
for @C { KheEjectionChainRepairResources } it is @C { "es_ejector2" }.
This allows for collecting two sets of statistics, for time
assignment and resource assignment.
@End @SubSection

@SubSection
    @Title { Limiting the scope of changes }
    @Tag { ejection.repair.limiting }
@Begin
@LP
Ejection chains work best when they are free to follow chains into
any part of a solution, and make any repairs that help.  This
freedom can conflict with the caller's desire to limit the scope
of the changes they make, typically because initial assignments
have not yet been made to some parts of the solution, and an
ejection chain repair should not anticipate them.
@PP
For example, suppose resource @M { r } is preassigned to some
tasks, and there are others it could be assigned to.  The
preassigned tasks go into @M { r }'s timetable when their meets
are assigned times, and could then create resource defects that
an ejection chain time repair algorithm knows about.  Suppose a
limit busy times underload defect is created (quite likely when
the workload on some day first becomes non-zero), and its augment
function tries (among other things) to assign more tasks to
@M { r } to increase its workload on that day.  This is not
done at present, but it is plausible.  Then there will be an
unexpected burst of resource assignment during time assignment.
@PP
One romantic possibility is to `let a thousand flowers bloom'
and just accept such repairs.  The problem with this is that
a carefully organized initial assignment can be much better
than the result of a set of uncoordinated individual repairs.
@PP
Another possibility is to fix the assignments of all variables
beyond the scope of the current phase of the solve to their
current values, often null values.  This is a very reliable
approach, and arguably the most truthful, because it says to the
ejection chain algorithm, in effect, `for reasons beyond your
comprehension, you are not permitted to change these variables.'
But it suffers from a potentially severe efficiency problem:  a
large amount of time could be spent in discovering a large number
of repairs, which all fail through trying to change fixed variables.
@PP
Yet another possibility is to have one ejector object for each
kind of call (one for repairing time assignments, another for
repairing resource assignments, and so on), with different
augment functions.  The augment functions for time repair would
never assign a task, for example.  This was the author's original
approach, but as the code grew it became very hard to maintain.
@PP
At present the author is using the following somewhat ad-hoc ideas
to limit the scope of changes.  They do the job at very little cost
in code and run time.
@PP
The start group monitor is one obvious aid to restricting the scope
of a call.  For example, time repair calls do not include event
resource monitors in their start group monitors.
@PP
Many repairs move meets and tasks, but do not assign them.  It seems
that once a meet or task has been assigned, it is always reasonable to
move it during repair.  So the danger areas are augment functions that
assign meets and tasks, not augment functions that merely move them.
@PP
Augment functions for assign time and assign resource defects must
contain `dangerous' assignments.  But suppose that the assign time
or assign resource monitor for some meet or task is not in the start
group monitor.  Then a repair of that monitor cannot occur first on any
chain; and if the meet or task is unassigned to begin with, it
cannot occur later either, since the monitor starts off with
maximum cost, so its cost cannot increase, and only monitors whose
cost has increased are repaired after the first repair on a
chain.  So assign time and assign resource augment functions can
be included without risk of the resulting time and resource
assignments being out of scope.  This is just as well,
since they are needed after ejecting meet and task moves.
@PP
If it can be shown, as was just done, that certain events will
remain unassigned, then they can have no other event defects,
since those require the events involved to be assigned.
Similarly, unassigned event resources will never give rise to
other event resource defects.
@PP
Another idea is to add options to the options object that control
which repairs are tried.  This is as general as different ejector
objects with different augment functions are, but, if the options
are few and clearly defined, it avoids the maintenance problems.
If many calls on augment functions achieve nothing because options
prevent them from trying things, that would be an efficiency problem,
but there is no problem of that kind in practice.
@PP
The options object contains an @C { es_repair_times } option,
which when @C { true } allows repairs that assign and move meets,
and an @C { es_repair_resources } option, which when @C { true }
allows repairs that assign and move tasks.  It takes virtually no
code or time to consult these options; often, just one test at the
start of an augment function is enough.
@PP
When moving a meet, its chain of assignments is followed upwards,
trying moves at each level.  But if the aim is to repair only a
small area (one runaround, say), then even if a repair starts
within scope, it can leave it as it moves up the chain.  This has
happened, and has caused problems.  So the options object contains
an @C { es_limit_node } option, whose value is a node.  If it is
non-@C { NULL }, meet assignments and moves are not permitted
outside its proper descendants.
@PP
{0.95 1.0} @Scale @C { KheEjectionChainNodeRepairTimes }
and {0.95 1.0} @Scale @C { KheEjectionChainLayerRepairTimes } set
option @C { es_repair_times } to @C { true },
@C { es_repair_resources } to @C { false }, and
@C { es_limit_node } to the parent node, or to @C { NULL } if it is
the cycle node.  The @C { false } value for @C { es_repair_resources }
solves the hypothetical problem, given as an example at the start
of this section, of limit busy times repairs assigning resources
during time assignment.
@PP
{0.95 1.0} @Scale @C { KheEjectionChainRepairResources } sets option
@C { es_repair_times } to @C { false }, option @C { es_repair_resources }
to @C { true }, and option @C { es_limit_node } to @C { NULL }.
Setting @C { es_repair_times } to @C { true } here would also
be reasonable, although slow; it would allow the repairs to try
meet moves while repairing task assignments.
@End @SubSection

@SubSection
    @Title { Correlation problems involving demand defects }
    @Tag { ejection.repair.demand }
@Begin
@LP
Section {@NumberOf time_structural.monitor} discusses the problem
of correlated monitors, and how it can be solved by grouping.
Demand monitors obviously correlate with avoid clashes monitors:
when there is a clash, there will be both an avoid clashes defect
and an ordinary demand defect.  They also correlate with avoid
unavailable times, limit busy times, and limit workload monitors:
when there is a hard avoid unavailable times defect, for example,
there will also be a demand defect.  This section explores several
ways of handling these correlations, beginning with grouping.
@PP
@I { Group the correlated monitors. }  Grouping is the ideal solution
for correlation problems, but it does not work here.  There are two
reasons for this.
@PP
First, although every avoid clashes defect has a correlated ordinary
demand defect, unless the resource involved is preassigned there is
no way to predict which monitors will be correlated, since that
depends on which resource is assigned to the demand monitors' tasks.
@PP
Second, grouping workload demand monitors with the resource monitors
they are derived from has a subtle flaw.  A demand defect is really
the whole set of demand monitors that compete for the insufficient
supply.  (These sets are quite unpredictable and cannot themselves
be grouped.)  A workload demand defect shows up, not in the workload
demand monitor itself, but in an ordinary demand monitor that
competes with it.  This is because the last demand tixel to change
its domain and require rematching is the one that misses out on the
available supply, and workload demand monitors never change their
domains.  So this grouping still leaves two correlated defects
ungrouped:  the group monitor and the unmatched ordinary demand monitor.
@PP
If grouping does not work, then one of the correlated monitors has
to be detached or otherwise ignored.  There are several ways to do this.
@PP
@I { Detach demand monitors. }  This does not work, because no-one
notices that six Science meets are scheduled simultaneously when
there are only five Science laboratories, and the resulting time
assignment is useless.
@PP
@I { Attach demand monitors but exclude them from continue group monitors. }
This prevents correlated monitors from appearing on defect lists,
but both costs continue to be added to the solution cost, so
removing the resource defect alone does not produce an overall
improvement.  The chain terminates in failure; the ejector cannot
see that repairing the resource defect could work.
@PP
@I { Attach demand monitors but exclude them from the solution and
continue group monitors; add a limit monitor holding them. }  Then
other monitors will be repaired, but no chain which increases the total
number of demand defects will be accepted.  This has two problems.
@PP
First, it can waste time constructing chains which fall at the last
hurdle when it is discovered that they increase demand defects.  This
is particularly likely during time repair:  the six Science meets
problem could well occur and pass unnoticed for a long time.
# Resource repair usually
# runs faster, and wasting time then does not matter so much.
@PP
Second, although it prevents demand defects from increasing, it
does not repair them.  This rules it out for time repair, which
is largely about repairing demand defects, but it may not matter
for resource repair.  Resource repair cannot reduce the number of
demand defects unless it moves meets:  merely assigning resources
reduces the domains of demand tixels, which cannot reduce demand
defects.  Even moving meets is unlikely to reduce demand defects
during resource repair, since many of those moves will have been
tried previously, during time repair.
@PP
@I { Detach correlated resource monitors. }  Instead of detaching
demand monitors, detach the resource monitors they correlate with.
Each kind of resource monitor is considered below.  The main danger
here is @I { inexactness }:  if some detached resource monitor is
not modelled exactly by the demand monitors that replace it, then
some defects go undetected and unrepaired.
@PP
@I { Detach all avoid clashes monitors. }  For every avoid clashes
defect there is an ordinary demand defect.  The only inexactness
is that avoid clashes monitors may have any weights, whereas demand
monitors have equal weight, usually 1 (hard).  But avoid clashes
constraints usually have weight 1 (hard) or more, so this does not
seem to be a problem in practice, given that, as the specification
says, hard constraint violations should be few in good solutions.
@PP
@I { Detach avoid unavailable times monitors that give rise to
workload demand monitors. }  These are monitors with weight at least 1
(hard).  The modelling here is exact apart from any difference in
hard weight, so again there is no problem in practice.
@PP
@I { Detach limit busy times monitors that give rise to workload
demand monitors. }  These are monitors with weight at least 1
(hard) which satisfy the subset tree condition
(Section {@NumberOf matchings.workload.tree}).  Apart from the
possible difference in hard weight, this is exact except for one
problem:  limit busy times constraints can impose both a lower
and an upper limit on resource availability in some set of times,
and workload demand monitors do not model lower limits at all.
@PP
This can be fixed by (conceptually) breaking each limit busy times
monitor into two, an underload monitor and an overload monitor, and
detaching the overload monitor but not the underload monitor.  KHE
expresses this idea in a different way, chosen because it also
solves the problem presented by limit workload monitors, to be
discussed in their turn.
@PP
Limit busy times monitors have two attributes, @C { Minimum } and
@C { Maximum }, such that less than @C { Minimum } busy times is an
underload, and more than @C { Maximum } is an overload.  Add a third
attribute, @C { Ceiling }, such that @C { Ceiling >= Maximum }, and
specify that, with higher priority than the usual rule, when the number
of busy times exceeds @C { Ceiling } the deviation is 0.
@PP
Function @C { KheLimitBusyTimesMonitorSetCeiling }
(Section {@NumberOf monitoring.limitbusy}) may be called to set the
ceiling.  Setting it to @C { INT_MAX } (the default value) produces
the usual rule.  Setting it to @C { Maximum } is equivalent to
detaching overload monitoring.
@PP
@I { Detach limit workload monitors that give rise to workload
demand monitors. }  Limit workload monitors are similar to limit
busy times monitors whose set of times is the entire cycle.
However, the demand monitors derived from a limit workload
monitor do not necessarily model even the upper limit exactly
(Section {@NumberOf matchings.workload.construct}).  This
problem can be solved as follows.
@PP
Consider a resource with a hard limit workload monitor and some
hard workload demand monitors derived from it, and suppose that
all of these monitors are attached.  As the resource's workload
increases, it crosses from a `white region' of zero cost into a
`grey region' where the limit workload monitor has non-zero cost
but the workload demand monitors do not, and then into a `black
region' where both the limit workload monitor and the workload
demand monitors have non-zero cost.  This black region is the problem.
@PP
The problem is solved by adding a @C { Ceiling } attribute to
limit workload monitors, as for limit busy times monitors.
Function @C { KheLimitWorkloadMonitorSetCeiling }
(Section {@NumberOf monitoring_resource_monitors_workload})
sets the ceiling.  As before, the default value
is @C { INT_MAX }.  The appropriate alternative value is not
@C { Maximum }, but rather a value which marks the start of
the black region, so that the limit workload monitor's cost
drops to zero as the workload crosses from the grey region
to the black region.  In this way, all workload overloads
are reported, but by only one kind of monitor at any one time.
@PP
There is one anomaly in this arrangement:  a repair that
reduces workload from the black region to the grey region
does not always decrease cost.  This is a pity but it is
very much a second-order problem, given that the costs
involved are all hard costs, so that in practice repairs
are wanted that reduce them to zero.  What actually happens is
that one repair puts a resource above the white zone, and this
stimulates a choice of next repair which returns it to the white
zone.  Repairs which move between the grey and black zones are
possible but are not likely to lie on successful chains anyway,
so it does not matter much if their handling is imperfect.
@PP
The appropriate value for @C { Ceiling } is the number of times
in the cycle minus the total number of workload demand monitors
for the resource in question, regardless of their origin.  When
the resource's workload exceeds this value, there will be at
least one demand defect, and it is time for the limit workload
monitor to bow out.
@PP
To summarize all this:  there is some choice during resource
repair, but detaching resource monitors (with ceilings) always
works, and it is the only method that works during time repair.
@End @SubSection

@SubSection
    @Title { Primary grouping and detaching }
    @Tag { ejection.repair.primary }
@Begin
@LP
To install and remove the primary groupings used by ejection chains, call
@ID @C {
void KheEjectionChainPrepareMonitors(KHE_SOLN soln,
  KHE_OPTIONS options);
void KheEjectionChainUnPrepareMonitors(KHE_SOLN soln);
}
This includes detaching some resource monitors, as in the plan
evolved in Section {@NumberOf ejection.repair.demand}.  This section
explains exactly what @C { KheEjectionChainPrepareMonitors } does.
@PP
@C { KheEjectionChainPrepareMonitors } partitions the events of
@C { soln }'s instance into classes, placing events into the same
class when following the fixed assignment paths out of their meets
proves that their meets must run at the same times.  It then groups
event monitors as follows.
@PP
@I { Split events and distribute split events monitors. } For each
class, it groups together the split events and distribute split
events monitors that monitor the events of that class.  It gives
sub-tag @C { KHE_SUBTAG_SPLIT_EVENTS } to any group monitors it
creates.  There is also a @C { KHE_SUBTAG_DISTRIBUTE_SPLIT_EVENTS }
subtag, but it is not used.
# Event splitting
# is handled structurally, so @C { KheEjectionChainPrepareMonitors }
# does nothing with these monitors.
# They usually have provably zero fixed cost, so are already detached.
@PP
@I { Assign time monitors. }  For each class, it groups the assign
time monitors that monitor the events of that class, giving sub-tag
@C { KHE_SUBTAG_ASSIGN_TIME } to any group monitors.
@PP
@I { Prefer times monitors. }  Within each class, it groups those
prefer times monitors that monitor events of that class whose
constraints request the same set of times, giving sub-tag
@C { KHE_SUBTAG_PREFER_TIMES } to any group monitors.
@PP
@I { Spread events monitors. }  For each spread events monitor, it
finds the set of classes that hold the events it monitors.  It
groups attached spread events monitors whose sets of classes are
equal, giving sub-tag @C { KHE_SUBTAG_SPREAD_EVENTS } to any group
monitors.  Strictly speaking, only monitors whose constraints
request the same time groups with the same limits should be grouped,
but that check is not currently being made.
@PP
@I { Link events monitors. }  Like split events monitors, these are
usually handled structurally, so it does nothing with them.  They
usually have provably zero fixed cost, so are already detached.
@PP
@I { Order events monitors. }  For each order events monitor, it
finds the sequence of classes that hold the two events it monitors.
It groups attached order events monitors whose sequences of classes
are equal, giving sub-tag @C { KHE_SUBTAG_ORDER_EVENTS } to any group
monitors.  Strictly speaking, only monitors whose constraints request
the same event separations should be grouped, but that check is not
currently being made.
@PP
Next, @C { KheEjectionChainPrepareMonitors } partitions the event
resources of @C { soln }'s instance into classes, placing event
resources into the same class when following the fixed assignment
paths out of their tasks proves that they must be assigned the same
resources.  It then groups event resource monitors as follows.
@PP
@I { Assign resource monitors. }  For each class, it groups the
assign resource monitors of that class's event resources, giving
sub-tag @C { KHE_SUBTAG_ASSIGN_RESOURCE } to any group monitors.
@PP
@I { Prefer resources monitors. }  Within each class, it groups those
prefer resources monitors that monitor the event resources of that
class whose constraints request the same set of resources, giving
sub-tag @C { KHE_SUBTAG_PREFER_RESOURCES } to any group monitors.
@PP
@I { Avoid split assignments monitors. }  There seems to be no
useful primary grouping of these monitors, so nothing is done with
them.  They may be handled structurally, in which case they will
have provably zero fixed cost and will be already detached.
@PP
Students who follow the same curriculum have the same timetable.  So
for each resource type @C { rt } such that a call to
{0.95 1.0} @Scale @C { KheResourceTypeDemandIsAllPreassigned(rt) }
(Section {@NumberOf resource_types})
shows that its resources are all preassigned,
{0.95 1.0} @Scale @C { KheEjectionChainPrepareMonitors }
groups the resource monitors of @C { rt }'s resources as follows.
@PP
@I { Avoid clashes monitors. }  It groups those avoid clashes monitors
derived from the same constraint whose resources attend the same
events, giving sub-tag
{0.95 1.0} @Scale @C { KHE_SUBTAG_AVOID_CLASHES } to any group monitors.
@PP
@I { Avoid unavailable times monitors. }  It groups avoid unavailable
times monitors derived from the same constraint whose resources attend
the same events, giving any group monitors sub-tag
@C { KHE_SUBTAG_AVOID_UNAVAILABLE_TIMES }.
@PP
@I { Limit idle times monitors }.  It groups limit idle times monitors
derived from the same constraint whose resources attend the same events,
giving sub-tag @C { KHE_SUBTAG_LIMIT_IDLE_TIMES } to any group monitors.
@PP
@I { Cluster busy times monitors. }  It groups cluster busy times monitors
derived from the same constraint whose resources attend the same events,
giving any group monitors sub-tag @C { KHE_SUBTAG_CLUSTER_BUSY_TIMES }.
@PP
@I { Limit busy times monitors. }  It groups limit busy times monitors
derived from the same constraint whose resources attend the same events,
giving sub-tag @C { KHE_SUBTAG_LIMIT_BUSY_TIMES } to any group monitors.
@PP
@I { Limit workload monitors. }  It groups limit workload monitors
derived from the same constraint whose resources attend the same events,
giving sub-tag @C { KHE_SUBTAG_LIMIT_WORKLOAD } to any group monitors.
@PP
@I { Limit active intervals monitors. }  It groups limit active intervals
monitors derived from the same constraint whose resources attend the same
events, giving sub-tag @C { KHE_SUBTAG_LIMIT_ACTIVE_INTERVALS } to any
group monitors.
@PP
Fixed assignments between meets are taken into account when deciding
whether two resources attend the same events.  As far as resource
monitors are concerned, it is when the resource is busy that matters,
not which meets it attends.
@PP
@C { KheEjectionChainPrepareMonitors } also treats some resource monitors
according to the plan from Section {@NumberOf ejection.repair.demand},
whether they are grouped or not:
@BulletList

@LI @OneRow {
It detaches all attached avoid clashes monitors.
}

@LI @OneRow {
For each attached avoid unavailable times monitor @C { m } for which
a workload demand monitor with originating monitor @C { m } is
present (all hard ones, usually), it detaches @C { m }.
}

@LI @OneRow {
For each attached limit busy times monitor @C { m } for which a
workload demand monitor with originating monitor @C { m } is
present (all hard ones satisfying the subset tree condition of
Section {@NumberOf matchings.workload.tree}, usually), if
@C { m }'s lower limit is 0 it detaches @C { m }, otherwise it
sets @C { m }'s @C { ceiling } attribute to its @C { maximum }
attribute, by calling @C { KheLimitBusyTimesMonitorSetCeiling }.
}

@LI @OneRow {
For each attached limit workload monitor @C { m } for which a workload
demand monitor with originating monitor @C { m } is present (all hard
ones, usually), it sets @C { m }'s @C { ceiling } attribute to the cycle
length minus the number of workload demand monitors for @C { m }'s
resource (regardless of origin), or 0 if this is negative, by
calling @C { KheLimitWorkloadMonitorSetCeiling }.
}

@EndList
Section {@NumberOf ejection.repair.demand} has the reasoning.
@PP
Finally, @C { KheEjectionChainPrepareMonitors } groups demand monitors as
follows.  If a limit monitor containing these monitors is wanted, a
separate call is needed (Section {@NumberOf ejection.repair.secondary}).
@PP
@I { Ordinary demand monitors. }  For each set of meets such that
the fixed assignment paths out of those meets end at the same meet,
it groups the demand monitors of those meets' tasks, giving sub-tag
@C { KHE_SUBTAG_MEET_DEMAND } to any group monitors.  The reasoning
is that the only practical way to repair an ordinary demand defect
is to change the assignment of its meet (or some other clashing meet),
which will affect all the demand monitors grouped with it here.
@PP
@I { Workload demand monitors. }  These remain ungrouped.  As
explained in Section {@NumberOf ejection.repair.demand},
workload demand defects appear only indirectly, as competitors
of ordinary demand defects.
@End @SubSection

@SubSection
    @Title { Secondary groupings }
    @Tag { ejection.repair.secondary }
@Begin
@LP
Section {@NumberOf time_structural.monitor} introduces the concept
of secondary groupings.  The three ejection chain functions need
secondary groupings built on primary groupings for their start group
monitors (but not their continue group monitors, since they use the
solution object for that), and other secondary groupings for their
limit monitors.  These are the subject of this section.
@PP
@C { KheEjectionChainNodeRepairTimes } uses the group monitor
returned by
@ID @C {
KHE_GROUP_MONITOR KheNodeTimeRepairStartGroupMonitorMake(KHE_NODE node);
}
as its start group monitor.  The result has sub-tag
@C { KHE_SUBTAG_NODE_TIME_REPAIR }.  Its children are monitors, or
primary groupings of monitors where these are already present, of
two kinds.  First are all assign time, prefer times, spread events,
order events, and ordinary demand monitors that monitor the meets
of @C { node } and its descendants, plus any meets whose assignments
are fixed, directly or indirectly, to them.  Second are all resource
monitors.  Only preassigned resources are assigned during time repair,
but those assignments may cause resource defects which can only be
repaired by changing time assignments, just because the resources
involved are preassigned.
@PP
@C { KheEjectionChainLayerRepairTimes } chooses one of the group monitors
returned by
@ID @C {
KHE_GROUP_MONITOR KheLayerTimeRepairStartGroupMonitorMake(
  KHE_LAYER layer);
KHE_GROUP_MONITOR KheLayerTimeRepairLongStartGroupMonitorMake(
  KHE_LAYER layer);
}
as its start group monitor, depending on option
@C { es_layer_repair_long }.  The result has
sub-tag @C { KHE_SUBTAG_LAYER_TIME_REPAIR }, with the same children
as before, only limited to those that monitor the meets and resources
of @C { layer }, or (if @C { es_layer_repair_long } is @C { true})
of layers whose index number is less than or equal to @C { layer }'s.
@PP
@C { KheEjectionChainRepairResources } uses the group monitor returned
by
@ID @C {
KHE_GROUP_MONITOR KheTaskingStartGroupMonitorMake(KHE_TASKING tasking);
}
for its start group monitor.  The result has sub-tag
@C { KHE_SUBTAG_TASKING }, and its children are the following
monitors (or primary groupings of those monitors, where those already
exist):  the assign resource, prefer resources, and avoid split
assignments monitors, and the resource monitors that monitor the
tasks and resources of @C { tasking }.  If the tasking is for a
particular resource type, only monitors of entities of that type
are included.
@PP
To allow an ejection chain to unassign meets temporarily but prevent
it from leaving meets unassigned in the end, a limit monitor is
imposed which rejects chains that allow the total cost of assign
time defects to increase.  This monitor is created by calling
@ID @C {
KHE_GROUP_MONITOR KheGroupEventMonitors(KHE_SOLN soln,
  KHE_MONITOR_TAG tag, KHE_SUBTAG_STANDARD_TYPE sub_tag);
}
passing @C { KHE_ASSIGN_TIME_MONITOR_TAG } and
@C { KHE_SUBTAG_ASSIGN_TIME } as tag parameters.
@PP
To prevent the number of unmatched demand tixels from increasing,
when that is requested by the @C { resource_invariant } option,
the group monitor returned by function @ID @C {
KHE_GROUP_MONITOR KheAllDemandGroupMonitorMake(KHE_SOLN soln);
}
is used as a limit monitor.  Its sub-tag is @C { KHE_SUBTAG_ALL_DEMAND },
and its children are all ordinary and workload demand monitors.  Primary
groupings are irrelevant to limit monitors, so these last two functions
take no account of them.
@End @SubSection

@SubSection
    @Title { Augment functions }
    @Tag { ejection.repair.augment }
@Begin
@LP
The augment functions passed to the ejector are private to KHE.
This section explains what they do in detail.  It may be somewhat
out of date.
@PP
The augment functions consult several options.  The
@C { es_repair_times }, @C { es_limit_node }, and
@C { es_repair_resources } options are particularly important
because they limit the scope of repairs.  They cannot be set
by the user---or rather, they can, but that would be futile
because they are reset within the main functions.
Any repair which assigns or moves a meet consults
@C { es_repair_times }, and only proceeds if it is
@C { true }.  It tries moving each ancestor of the meet, since
that will also move the original meet; but if
@C { es_limit_node } is non-@C { NULL }, it omits moves
of meets lying within nodes which are not proper descendants of
@C { es_limit_node }.  Any repair which assigns or moves a
task consults @C { es_repair_resources }, and only
proceeds if it is @C { true }.
@PP
Here is the full list of repair operations executed by KHE's
augment functions.
@PP
@I { Node swaps }, which use @C { KheNodeMeetSwap }
(Section {@NumberOf time_solvers.misc}) to swap the assignments of
the meets of two nodes.  If the @C { es_nodes_before_meets }
option is @C { true }, then if node swaps are tried at all, they
are tried before (rather than after) meet moves.
@PP
@I { Basic and ejecting meet assignments and moves and Kempe meet
moves }, which move meets (Section {@NumberOf time_solvers.kempe}).
Where it is stated that a Kempe meet move is tried, it is in fact
tried only when the @C { es_kempe_moves } option
(Section {@NumberOf ejection.repair}) is @F { true }, or it is
@F { large_layers } and the meet to be moved lies in at least
one layer whose duration is at least 80% of the duration of the
cycle.  Where it is stated that an ejecting meet assignment or
move is tried, a basic meet assignment or move is tried instead
when the @C { es_no_ejecting_moves } option is @C { true }.
@PP
@I { Fuzzy meet moves }, which move meets in a more elaborate way
(Section {@NumberOf time_solvers.repair.meet_set}).  These are not
mentioned below, but they are tried after Kempe and ejecting meet
moves, although only when the @C { es_fuzzy_moves } option
is @C { true } and the current length is 1.
@PP
@I { Split moves }, which split a meet into two and Kempe-move one of
the fragments, and @I { merge moves }, which Kempe-move one meet to
adjacent to another and merge the two fragments.  As well as being used
to repair split defects, split moves are used similarly to fuzzy meet
moves:  although not mentioned below, they are tried after Kempe and
ejecting meet moves, but only when the @C { es_split_moves }
option is @C { true } and the current length is 1.  These Kempe meet
moves are not influenced by the @C { es_kempe_moves } option.
@PP
@I { Ejecting task assignments and moves }, which assign or move a
task to a given resource and then unassign any clashing tasks
(Section {@NumberOf resource_solvers.kempe}).
@PP
@I { Ejecting task-set moves }, which use ejecting task moves to
move a set of tasks to a common resource, succeeding only if all
of the moves that change anything succeed.
@PP
@I { Meet-and-task moves }, which Kempe-move a meet at the same time
as moving one of its tasks, succeeding only if both moves succeed.
@PP
Each repair is enclosed in calls to @C { KheEjectorRepairBegin }
and @C { KheEjectorRepairEnd } as usual.  In the more complex cases,
such as the last two on the list above, the @C { success } argument
of @C { KheEjectorRepairEnd } is set to @C { true } only if all
parts of the repair succeed.  Some of the more complex repairs are
tried only when the current length is 1, that is, when the defect
being repaired is truly present, not introduced by some other repair.
# @PP
# A @I { complex repair } is a combined repair that changes more
# than just a few entities.  Complex repairs are problematical,
# because of the many choices, and because they are likely to
# introduce many defects.  If they are used at all, it seems best
# to aim for a moderate number of changes, and a moderate number
# of alternatives.  To conserve running time, it may be best to
# allow them only at length 1, limiting them to repairing real
# defects, not defects introduced by previous repairs.
# @PP
# Complex repairs are similar to VLSN searches:  both rearrange a
# large chunk of the solution.  However, there are differences.
# A VLSN search has a single outcome which aims to reduce the cost
# of the solution.  A complex repair is one element of a set of
# alternative repairs, and it aims to either reduce the cost of
# the solution or else introduce just one significant new defect.
@PP
Some alternative repairs are naturally tried one after another.
The ejecting task moves of a given task to each resource in its
domain is one example.  Here are three less obvious, but nevertheless
very useful sequences of alternative repairs.
@PP
A @I { Kempe"/"ejecting meet move } is a sequence of one or two
alternative repairs, first a Kempe meet move, then an ejecting
meet move with the same parameters.  The ejecting meet move is
omitted when the Kempe meet move reports that it did only what
a basic meet move would have done, since in that case the
ejecting move is identical to the Kempe move.  This sequence
is similar to making an ejecting move and then, on the next
repair, favouring a particular reassignment of the ejected
meet(s) which is likely to work well.  Fuzzy and split moves
may follow the Kempe and ejecting meet moves, as explained above.
@PP
A @I { resource underload repair } for resource @M { r } and time
group @M { g } is a sequence of alternative repairs which aim to
increase the number of times @M { r } is busy within @M { g }.
Unless @M { g } is the whole cycle, for each task assigned @M { r }
whose @I overlap (the number of times it is running within @M { g })
is less that its duration, it tries all ejecting meet moves of the
task's meet which increase its overlap.  After that it tries an
ejection tree repair like the one described below for cluster busy
times defects, which aims to empty out the entire time group---a
quite different way to remove the defect.
@PP
A @I { resource overload repair } for resource @M { r } and time
group @M { g } is a sequence of alternative repairs which aims to
decrease the number of times @M { r } is busy within @M { g }.
First, for each task assigned but not preassigned @M { r } whose
overlap is non-zero, it tries all ejecting task moves of the task
to its domain's resources.  Then, for each task assigned (including
preassigned) @M { r } whose overlap is non-zero, it tries all
ejecting meet moves of the task's meet which decrease the overlap.
@PP
Wherever possible, sequences of alternative repairs change the
starting point of the traversal of the alternatives on each call.
For example, when trying alternative resources, the code is
@ID @C {
for( i = 0;  i < KheResourceGroupResourceCount(rg);  i++ )
{
  index = (KheEjectorCurrAugmentCount(ej) + i) %
    KheResourceGroupResourceCount(rg);
  r = KheResourceGroupResource(rg, index);
  ... try a repair using r ...
}
}
The first resource tried depends on the number of augments so far,
an essentially random number.  This simple idea significantly
decreases final cost and run time.
@PP
Following is a description of what each augment function does when
given a non-group monitor with non-zero cost to repair.  When given
a group monitor with non-zero cost, since the elements of a group
all monitor the same thing in reality, the augment function takes
any individual defect from the group and repairs that defect.
@PP
@I { Split events and distribute split events defects. }  Most
events are split into meets of suitable durations during layer
tree construction, but sometimes the layer tree does not remove all
these defects, or a split move introduces one.  In those cases, the
split analyser (Section {@NumberOf time_structural.split.analyse})
from the @C { options } object is used to analyse the defects and
suggest splits and merges which correct them.  For each split
suggestion, for each meet conforming to the suggestion, a repair
is tried which splits the meet as suggested.  For each merge
suggestion, for each pair of meets conforming to the suggestion,
four combined repairs are tried, each consisting of, first, a
Kempe meet move which bring the two meets together, and second,
the merge of the two meets.  The four Kempe moves move the first
meet to immediately before and after the second, and the second to
immediately before and after the first.
@PP
@I { Assign time defects. }  For each monitored unassigned meet,
all ejecting meet moves to a parent meet and offset that would
assign the meet to a time within its domain are tried.
@PP
@I { Prefer times defects. }  For each monitored meet assigned
an unpreferred time, all Kempe"/"ejecting meet moves to a
parent meet and offset giving a preferred time are tried.
@PP
@I { Spread events defects. }  For each monitored meet in an
over-populated time group, all Kempe"/"ejecting moves of the
meet to a time group that it would not over-populate are tried;
and for each under-populated time group, for each meet whose
removal would not under-populate its time group, all
Kempe"/"ejecting moves of it to the under-populated time group
are tried.
@PP
@I { Link events defects. }  These are not repaired; they are
expected to be handled structurally.
@PP
@I { Order events defects. }  These are currently ignored.  It will
not be difficult to find suitable meet moves in the future.
@PP
@I { Assign resource defects. }  For each monitored unassigned task,
all ejecting assignments of the task to resources in its domain are
tried.  Then if the @C { es_repair_times } option permits, all
combinations of a Kempe meet move of the enclosing meet and an
ejecting assignment of the task to resources in its domain are tried.
@PP
@I { Prefer resources defects. }  For each monitored task assigned
an unpreferred resource, all ejecting moves of the task to
preferred resources are tried.
@PP
@I { Avoid split assignments defects. }  For each resource participating
in a split assignment there is one repair:  all involved tasks
assigned that resource are unassigned, all involved tasks' domains
are restricted to the other participating resources, and a set of
ejection chains is tried, each of which attempts to reassign one of
the unassigned tasks.  The repair succeeds only if all these chains
succeed, making an ejection tree, not a chain.  This is expensive
and unlikely to work, so it is only tried when the defect is a main
loop defect or only one task needs to be unassigned.
# @I { Avoid split assignments defects. }  For each pair of resources
# assigned to monitored tasks, and for each resource in the domain of
# one of those tasks, an ejecting task-set move of those tasks to that
# resource is tried.  Then, for each resource assigned to exactly one
# of the monitored tasks, a meet-and-task move of that task's meet to
# a different time is tried, combined with a move of the task to one
# of the other resources assigned to any of the monitored tasks.
@PP
@I { Avoid clashes defects. }  Avoid clashes monitors are detached
during ejection chain repair, since their work is done by demand
monitors.  So there are no avoid clashes defects.
# For each clashing task, try moving it
# to any other resource in its domain.  This function expects to be called
# only during resource repair, because during time repair avoid clashes
# monitors are usually detached.
# Even during resource repair it would probably be better to ignore
# these defects, since they are basically unrepairable then anyway.
@PP
@I { Avoid unavailable times defects. }  A resource overload
repair (see above) for the monitored resource and the unavailable
times is tried.
@PP
@I { Limit idle times defects. }  For each task assigned the
monitored resource at the start or end of a `day' (actually, a time
group being monitored), each Kempe"/"ejecting meet move of that
task's meet such that the initial meet move reduces the number of
idle times for that resource is tried.  Calculating this condition
is not trivial, but the augment function does it exactly.  Task moves
could help to repair limit idle times defects for unpreassigned
resources, but in current data sets the resources involved are
usually preassigned, so task moves are not currently being tried.
@PP
After the repairs just given are tried, if the repair has length 1
(if the defect was not created by a previous repair on the current
chain), a complex repair is tried which eliminates all idle times for
one resource on one day.  Take the meets assigned that resource on
that day.  A retimetabling of those meets on that day with no clashes
and no idle times is defined by a starting time for the first meet
and a permutation of the meets (their chronological order in the
assignment).  If there are @M { k } meets and @M { s } starting
times that don't put the last meet off the end of the day, then
there are @M { s cdot k! } retimetablings in total.  In practice
this is a moderate number.  For safety, only a limited number of
retimetablings is tried, by switching to a single permutation at
each new node after a fixed limit (currently 1000) is reached.
# , especially if we prune the repairs as
# they are being generated, by allowing at most one assignment that
# increases the number of unmatched demand tixels (two would not be
# repairable on the next step anyway).
@PP
@I { Cluster busy times defects. }  If the resource is busy in too
few monitored time groups, all ejecting meet moves are tried which
move a meet which is not the only meet in a monitored time group
(either because every monitored time group it overlaps with overlaps
with at least one other meet, or because it does not overlap with
any monitored time groups) to a monitored time group in which it
is.  If the resource is busy in too many monitored time groups,
then for each monitored time group @C { tg } containing at least
one meet, if the length is 1 or @C { tg } contains exactly one meet,
all the meets in @C { tg } are unassigned, and @C { tg } and all
monitored time groups containing no meets are removed from the
domains of all meets assigned the resource.  This is an ejection
tree repair if more than one meet is unassigned:  all of the
unassigned meets must be reassigned for success.  The repair
must ensure that the domains are restored on success as well
as on failure.
# @C { on_success_fn } parameter of @C { KheEjectorRepairEndLong }
# is used to ensure that the domains are restored on success as well
# as on failure.
@PP
@I { Limit busy times defects. }  For each set of times when
the resource is underloaded (resp. overloaded), a resource
underload (resp. overload) repair of the resource at those
times is tried.
@PP
@I { Limit workload defects. }  If the resource is overloaded,
a resource overload repair is tried, taking the set of times to be
the entire cycle.  There is currently no repair for underloads.
@PP
@I { Ordinary and workload demand defects. }  If the defect has
a workload demand monitor competitor (possibly itself, although
workload demand monitors rarely fail to match), a resource overload
repair is tried for the workload demand monitor's resource and
the domain of its originating monitor.  If the defect has no
workload demand defect competitor, all ejecting meet moves of
competitor meets to anywhere different are tried; but meets
within vizier nodes are not moved in this case, since that
would only shift the problem to a different time.
@End @SubSection

@SubSection
    @Title { Repair operations for nurse rostering }
    @Tag { ejection.repair.nurse }
@Begin
@LP
@I { This section is obsolete.  It will be rewitten eventually.
Meanwhile, see my KHE18 nurse rostering solver paper. }
@PP
Apart from the absence of time assignment, nurse rostering is
characterized by constraints that relate what a resource does on
one day to what it does on adjacent days.  Because of them, it
makes sense to use repairs which move small sets of tasks, from
adjacent days, as a block from one resource to another.  Many
authors of nurse rostering solvers have come to this conclusion.
@PP
Suppose that a defect arises that might be repaired by an augment
that moves @C { task } from @C { r1 } to @C { r2 }, as carried out
by @C { KheTaskMoveAugment }.  Either of @C { r1 } and @C { r2 },
but not both, may be @C { NULL }.  The idea offered here is that
in the nurse rostering case, @C { KheTaskMoveAugment } should
reinterpret a request to move @C { task } as a request to move
several tasks from adjacent days, including @C { task }.
@PP
Which tasks should be chosen?  Initially, they should all be assigned to
whatever @C { task } is assigned to initially (possibly @C { NULL }),
otherwise the rationale for this kind of move is lost.  The number of
adjacent days to include tasks from is harder to be sure about, except
that, as we search in each direction from @C { task }'s day, we are
obliged to stop after we reach the first or last day, and if there is
no task assigned what @C { task } is assigned to initially.  Furthermore,
if @C { r2 } is already busy on some day, it is not likely to be useful
to give it another task on that day.  So the search should stop at the
first case of that.  So we have a different set of moves to make for
each @C { r2 }.
@PP
Our plan is to move just @C { task } to begin with, then @C { task }
plus one day, then @C { task } plus 2 days, and so on up to @C { k }
days, where @C { k } is a parameter that we can choose, by means of
an option perhaps.  We will try moving all sets of from 1 to @C { k }
days that include @C { task }.  Clearly, @C { k } must be small, say
4 at most.  Only @C { task } will be marked visited and checked for
being visited.
# Many of these constraints are limit active intervals constraints,
# and so we take the presence of one or more of these as an indicator
# that resource repairs should change to accommodate them.
@PP
The set of possible repairs here has two dimensions, one indexed
by sets of days to move, the other by @C { r2 }.  We could iterate
in any order that maximizes efficiency.  It would be good, for
example, to build task sets as required, and remember them
from one resource to the next.
@PP
We also need to be able to handle `negative moves', where we
have a time group rather than a task and the idea is to move
something out of that time group or into it.  This is done
now by @C { KheNurseOverload }, but not very effectively.
# @PP no - we always want the frame to be days
# Another issue arises when the defect being repaired is from a
# constraint whose underlying frame is a sequence of something other
# than days, for example night shifts or weekends.  In such cases,
# replace `days' above by `night shifts', `weekends', or whatever.
# This `frame context' is a parameter of the repair.  If there is no
# frame context (e.g. cover constraints), it defaults to days.
@End @SubSection

@EndSubSections
@End @Section

@EndSections
@End @Chapter
