KHE diary for 2022
==================

At the end of 2021 I was testing the dynamic solver and working
on optimizing it.

30 December 2021.  Thinking about pruning during SolnExtend.  Basically
  worked out what to do, but not how to organize it.

31 December 2021.  Revised task classes to return asst_cost and
  non_asst cost.  Documented, clean compile, and audited, ready
  to run.  Then I need to use asst_cost and non_asst_cost in the
  dynamic solver.  If asst_cost is too high, don't try the task;
  if non_asst_cost is too high, must try the task.

  Done a complete implementation of must-assign tasks.  Needs
  an audit and testing.

1 January 2022.  Audited and documented must-assign tasks.  All good.

2 January 2022.  Working on making a one-to-one correspondence between
  submodules of the implementation and sub-appendices of the documentation.
  So far I have installed a two-level structure; at the top level is

    Introduction
    Resources
    Days
    Tasks
    Expressions
    Solutions
    Solving
    Expression details
    Testing

3 January 2022.  Moved solutions to before expressions and did
  some serious reorganizing of dominance testing.  The question
  now is, can the implementation be brought into line with this?

4 January 2022.  Code and doc in sync now up to the end of the
  Packed solutions section and submodule.

5 January 2022.  Finished off solving, in doc and code.  The
  remainder of the code seems to be in pretty good shape:
  first expressions, then construction, the public functions,
  and finally testing.

6 January 2022.  Documenting the reorganized Expressions
  submodules, starting from scratch basically.

7 January 2022.  Still documenting the reorganized Expressions
  submodules.  Going well, up to the start of the INT_SEQ_COST section.

8 January 2022.  Finished documenting the reorganized Expressions
  submodules, and did a few other bits of tidying up, including
  adding a Construction section to the Expressions section.  That
  finishes off the coordination of implementation and documentation
  that I began work on on 2 January.  So that's all good.  Taking a
  few days off to catch up with a backlog in my refereeing work.

15 January 2022.  Finished the refereeing work, back here now.
  Had an idea for "trade-off dominance", which I have started
  to implement.  All documented, and the boilerplate is mostly
  done.  Added a tradeoff_cost field to the dominance test objects
  and made sure it is initialized correctly.

16 January 2022.  Audited the implementation and documentation of
  strong dominance with tradeoff.  Made sure that tradeoff dominance
  is turned off when the cost function is quadratic.

17 January 2022.  Working on KheDrsSolnTrieStrongAddSolnWithTradeoff.
  Decided to make it heuristic, it tries one extra array position at
  each level.  All done, needs a careful audit, especially
  KheDrsSolnTrieRemoveDominated.

19 January 2022.  Audited the new code, have clean compile, ready to
  test.  Also audited the documentation.  Here are two test runs with
  rs_drs_dom_kind=trie:

  KheDrsSolveSearch ending day 1Mon (621 solns)
  KheDrsSolveSearch ending day 1Tue (3456 solns)
  KheDrsSolveSearch ending day 1Wed (4861 solns)
  KheDrsSolveSearch ending day 1Thu (10961 solns)
  KheDrsSolveSearch ending day 1Fri (90091 solns)
  KheDrsSolveSearch ending day 1Sat (24373 solns)
  KheDrsSolveSearch ending day 1Sun (1 solns)

  KheDrsSolveSearch ending day 1Mon (157 solns)
  KheDrsSolveSearch ending day 1Tue (1439 solns)
  KheDrsSolveSearch ending day 1Wed (2880 solns)
  KheDrsSolveSearch ending day 1Thu (2121 solns)
  KheDrsSolveSearch ending day 1Fri (13 solns)
  KheDrsSolveSearch ending day 1Sat (1 solns)

  And here they are again with rs_drs_dom_kind=trie_with_tradeoff:

  KheDrsSolveSearch ending day 1Mon (621 solns)
  KheDrsSolveSearch ending day 1Tue (2600 solns)
  KheDrsSolveSearch ending day 1Wed (2706 solns)
  KheDrsSolveSearch ending day 1Thu (6946 solns)
  KheDrsSolveSearch ending day 1Fri (48518 solns)
  KheDrsSolveSearch ending day 1Sat (12303 solns)
  KheDrsSolveSearch ending day 1Sun (1 solns)

  KheDrsSolveSearch ending day 1Mon (157 solns)
  KheDrsSolveSearch ending day 1Tue (1157 solns)
  KheDrsSolveSearch ending day 1Wed (1605 solns)
  KheDrsSolveSearch ending day 1Thu (1125 solns)
  KheDrsSolveSearch ending day 1Fri (13 solns)
  KheDrsSolveSearch ending day 1Sat (1 solns)

  The numbers are smaller, so it's so far so good.  What
  we really need now is a running time test.

20 January 2022.  I've documented a fairly elaborate testing function
  to put into the VLSN module.  I've audited the documentation; it's
  reasy to implement.

21 January 2022.  Working on KheDynamicResourceVLSNTest, I've finished
  the reading of rs_drs_test, have to run what I found next.

22 January 2022.  Working on KheDynamicResourceVLSNTest.  Working on
  KheRunTest.  I'm parsing the run well now, and executing it, so
  just statistics gathering and printing to do now.

23 January 2022.  Started work on statistics.  Very tedious.  Finished
  functions for adding and retrieving statistics, also functions for
  retrieving table size and running time each day (documented these).

24 January 2022.  Updated the type declarations for dimensions to be
  an inheritance structure with fields for ranges and fields for singles,
  and brought the functions into line.  Finished KheRunItemFileName.

25 January 2022.  Working on KheRunTest, statistics printing.

26 January 2022.  Working on KheRunTest, statistics printing.  All
  done, ready to audit and test.  Displaying every second item if
  number of items exceeds 25.

27 January 2022.  PATAT paper submission deadline is now Wed 23 March,
  which gives me another eight weeks to make something of dynamic.

28 January 2022.  Audited the stats code and changed it quite a lot,
  to make it clear that the singles arrays along the dimensions are
  essentially the same as the elements of the stats array.  All
  seems good now and ready to test.  Removed the old testing
  function from khe_sr_dynamic_resource.c.

  Started testing.  Found and fixed several bugs.  I now have
  a collection of promising looking files in stats.  I need to
  convert them to EPS files and see how they look, and whether
  they are what I asked for.

29 January 2022.  Looking at some EPS files, all good although
  there was one bug where the stats were measuring the rerun,
  not the original run.  Fixed now, all good.  Is there a
  transitivity result that proves size(strongt) <= size(strong)?

  When running three resources there is no comparison, strongt
  is far faster than strong.  But are we starting from the same
  solution?  Very possibly not.

30 January 2022.  Made sure each test solve starts from the same
  solution (by using a mark to undo any changes), and uses the
  same resources (it was doing that already).  Also now checking
  that runs with the same resources and days but different dom
  kinds produce solutions with the same cost (KheStatsAddCost).

  Fixed a bug:  a mark is not good enough, the changes KheMarkEnd
  makes are not reflected in the state of the solver.  So there
  is now a "test only" version of the solve operation, which does
  not change the solution.  All documented and implemented.

  Changed the AL feature to be L or epsilon and made it days only.
  Also found and fixed a bug in the implementation of it.

31 January 2022.  Did some test runs.  For the two fastest dom
  types, strongt and triet, it's taking about 10 seconds to
  solve for 3 resources over two weeks.

  Updated the documentation of graph printing; a few things have changed.

  Now getting better resource type names from the resource type
  partitioning code.  I've documented what it does in the User's Guide.

1 February 2022.  Looked at stats_4r.  The fastest was triet but
  it ran for 5000 secs (about 83 minutes) on 4 Trainee resources,
  because it got up to about 2,000,000 solutions per day at one
  point.  (Nurse was better, probably because it randomly chose
  some quite heterogeneous resources, which led to fewer viable
  combinations.)  Surely we can do better than this.

  Measured how many cases of dominance involve two solutions
  that are derived from the same solution, or the same solution
  two steps back.  There might be a locality of reference thing.
  And indeed there is:

    ancestor frequencies: 0 43900 36728 54473 14118 0 0 26409 239876

  This example shows that 43900 cases of dominance were between
  solutions with the same parent.  A caching scheme would help.
  In fact, it might help a lot.

  Started implementing a very general caching scheme.  Have a
  clean compile, but there are a few things still to do.

2 February 2022.  Added caching as an option to rs_drs_test.  Have
  clean compile of the whole thing, ready to audit and test.

3 February 2022.  Audited the new caching code in both files,
  it's ready to run.

4 February 2022.  Running the new caching code today on 3 resources.
  Results were underwhelming:
  
  strongt vs strongt@
  -------------------
  Got identical table sizes, which we want.  Running times are
  improved, by 50% in one case (to 5 secs) but only 10% in the
  other (to 30 secs).

  triet and triet@
  ----------------
  The table sizes are not quite identical, but that makes sense.
  There is very little difference between triet and triet@.  Running
  times are 9 secs and 20 secs.  So it seems that trie optimization
  is already doing what caching is doing.

  Off-site backup today.

5 February 2022.  Found a couple of bugs in extend_cost which seem
  to have had the effect of turning it off.  But turning it on has
  made very little difference.

  Still getting stuff like this:

    KheDrsSolveSearch ending day 1Thu (368062 solns made, 11040 kept)
    ancestor frequencies: 0 155251 56769 36061 108941

  There is a huge opportunity for optimizing here, if only I
  could find a way.

  Trying single day cost lower bounds, but I got this mad value:

    at end of day 2Sun, single_day_min_cost = 1.00050

  Why did I get this?  Most of the others were 0, but there is
  enough there to suggest that this is worth doing.  Can I
  find a more permanent implementation?  Could it be done
  when the expression opens?  I need to write a lot more
  code and I suspect, based on looking through the instance,
  that I would not get anything useful from it in the end.

  Here's a monster run I did:

    [ KheDrsSolveSearch(4 resources, 14 days)
      KheDrsSolveSearch ending day 1Mon (621 solns made, 621 kept)
      KheDrsSolveSearch ending day 1Tue (18120 solns made, 2784 kept)
      KheDrsSolveSearch ending day 1Wed (10692 solns made, 5160 kept)
      KheDrsSolveSearch ending day 1Thu (42152 solns made, 10695 kept)
      KheDrsSolveSearch ending day 1Fri (700346 solns made, 128712 kept)
      KheDrsSolveSearch ending day 1Sat (7347783 solns made, 762494 kept)
      KheDrsSolveSearch ending day 1Sun (10405567 solns made, 884343 kept)
      KheDrsSolveSearch ending day 2Mon (23285170 solns made, 464835 kept)
      KheDrsSolveSearch ending day 2Tue (24464911 solns made, 1351475 kept)
      KheDrsSolveSearch ending day 2Wed (86531440 solns made, 2012675 kept)
      KheDrsSolveSearch ending day 2Thu (104263007 solns made, 1591019 kept)
      KheDrsSolveSearch ending day 2Fri (2952796 solns made, 134626 kept)
      KheDrsSolveSearch ending day 2Sat (2373191 solns made, 104333 kept)
      KheDrsSolveSearch ending day 2Sun (9668 solns made, 1 kept)
    ] KheDrsSolveSearch returning true

  Actually it shows how big the solves can get, even with only
  four resources.  This was triet@, whole run took 98.4 mins.

6 February 2022.  We can save time by calculating the cost and
  signature of assigning each resource to each task just once,
  and then overly costly tasks can be skipped altogether and
  the others can contribute their costs and signatures in
  constant time.  But this will require "signature segments"
  containing a cost and a signature, and a solution will need
  one signature segment for each resource, plus one for the
  event resource part of the signature (usually empty).

  Also move dt->asst_cost and dt->non_asst_cost into the main
  stream and omit their expressions.

  Could do it in two stages, the first copying the segments
  into conventional signatures, the second never copying them.

  Starting work on type KHE_DRS_SIG_SEG, one segment of a
  signature.

7 February 2022.  Still thinking about KHE_DRS_SIG_SEG.  A
  separate type from KHE_DRS_SOLN does make sense, because
  an ss will be re-used by all solutions that assign a
  particular resource to a particular task set, starting
  from a particular previous solution.  So we clear them
  at the start of extending that solution, then build them
  all for that solution, then go into the combinations.
  We can't overwrite them unless we copy them first.  So
  let's copy them then.  Also, that way we save memory when
  solutions are deleted.

  Started on the implementation, by building KHE_DRS_SIG_SEG.
  I've done quite a lot, what remains is to get the actual
  values into the sig seg, parallel to EvalDay, by implementing
  KheDrsExprEvalSigSeg.

  Setting up drd->internal_today etc. correctly.  Next thing
  is to get sig_seg indexes parallel to sig_indexes.

  Completed KheDrsExprEvalSigSeg by cutting and pasting
  KheDrsExprEvalDay.  Not pretty but it should work.

8 February 2022.  Pushed through and now have what purports to
  be a complete implementation of the new version, where sigs
  are pre-computed before we generate all combinations.  But
  there is a lot of auditing to do, and some improvements to make.

9 February 2022.  Sorted out task classes so that only easily identified
  monitors contribute to *asst_cost and *non_asst_cost.  Now we can
  safely omit expressions for monitors that contribute to *asst_cost,
  and we are doing that.  And *asst_cost is already being included
  in the cost of each solution as we assign each task, so this little
  job is all done.

  Thought about doing dominance testing following each assignment of a
  resource, rather than following each assignment of the whole set of
  resources.  We might be able to cut down the number of edges that
  way.  But need to be careful about correctness.  In effect it would
  be one table for each day + k assignments on that day, for each k.
  The problem is, it would require event resource monitors to
  contribute to signatures in ways that they don't do now.  Scrap.

10 February 2022.  Working on getting reruns back today.  All done,
  needs an audit and test.

  Decided to continue with setting drd->internal_today each time
  we open for solving, even though it could be done once and for
  all when the solver is created, like drd->external_today.  The
  problem is that it's harder to see, when opening, which days
  drd we need to add e to.

11 February 2022.  Made KHE_DRS_SOLN a subtype of KHE_DRS_SIG_SEG,
  and renamed KHE_DRS_SIG_SEG to KHE_DRS_SIGNATURE.  Got rid of
  KheDrsExprEvalDay, it's replaced by KheDrsExprEvalSignature now.

12 February 2022.  Working on the documentation.  Went through the
  SolnExtend section, but now I have to revise it  to document the
  pre-making of signatures.  I've written an introduction to it, at
  the end of the "Solutions and signatures" section.

13 February 2022.  Working on the documentation.

14 February 2022.  Still working on the documentation.  I'm up to
  packed solutions, which means that I am through the two changed
  things:  optimizing resource constraint evaluation, and caching.

15 February 2022.  Still working on the documentation.  Did
  searching and solving today, all good.  Started on expressions;
  had to sort out sig_indexes and signature_indexes, which I've
  done successfully, so the path should be clear now.

16 February 2022.  Finished auditing the documentation.

17 February 2022.  Took today off doing other things.

18 February 2022.  Started testing.  Something seems to be wrong,
  they are all pruning down to an empty table after a few days.
  Granted that I am doing more pruning now, this seems excessive.

  Looked at asst_cost and non_asst_cost, their values are fine.
  Looked carefully over the code, it all looks good.  I'm going
  to have to do some serious debugging, which may be feasible
  given the small number of solutions being generated.

19 February 2022.  Testing.  Fixed yesterday's bug, it was two
  nested loops with the same index variable - Grrr.

  Did a large solve with 4 resources but it crashed a long way
  in, apparently it ran out of memory:

    KheDrsSolveSearch ending day 1Mon (621 solns made, 621 kept)
    KheDrsSolveSearch ending day 1Tue (18120 solns made, 3456 kept)
    KheDrsSolveSearch ending day 1Wed (13176 solns made, 8658 kept)
    KheDrsSolveSearch ending day 1Thu (70020 solns made, 26620 kept)
    KheDrsSolveSearch ending day 1Fri (1749208 solns made, 268556 kept)
    KheDrsSolveSearch ending day 1Sat (14680319 solns made, 1514225 kept)
    KheDrsSolveSearch ending day 1Sun (18912451 solns made, 2304988 kept)
    KheDrsSolveSearch ending day 2Mon (54166226 solns made, 2571874 kept)
    KheDrsSolveSearch ending day 2Tue (132570969 solns made, 9016431 kept)
    KheDrsSolveSearch ending day 2Wed (424,596,187 solns made, 17,156,617 kept)

  I have an older four resource run which got up to about 2,000,000
  table entries, much less than the 17,156,617 we have here.  It
  was triet.  This one is just trie.  Much better with triet only:

    KheDrsSolveSearch ending day 1Mon (621 solns made, 621 kept)
    KheDrsSolveSearch ending day 1Tue (18120 solns made, 2784 kept)
    KheDrsSolveSearch ending day 1Wed (10692 solns made, 5160 kept)
    KheDrsSolveSearch ending day 1Thu (42152 solns made, 10695 kept)
    KheDrsSolveSearch ending day 1Fri (700346 solns made, 128714 kept)
    KheDrsSolveSearch ending day 1Sat (7347831 solns made, 762988 kept)
    KheDrsSolveSearch ending day 1Sun (10420677 solns made, 885007 kept)
    KheDrsSolveSearch ending day 2Mon (23294928 solns made, 482384 kept)
    KheDrsSolveSearch ending day 2Tue (25469934 solns made, 1400088 kept)
    KheDrsSolveSearch ending day 2Wed (87,950,480 solns made, 2,030,602 kept)
    KheDrsSolveSearch ending day 2Thu (105,077,242 solns made, 1,591,139 kept)
    KheDrsSolveSearch ending day 2Fri (2952665 solns made, 134565 kept)
    KheDrsSolveSearch ending day 2Sat (2372636 solns made, 104281 kept)
    KheDrsSolveSearch ending day 2Sun (9668 solns made, 1 kept)

  So we found a new best here, which is great.
  
  A later rerun (two resources) is wrong:  KheDrsSolnGetTaskSignature
  crashed.  I've probably fixed it; KheDrsSolnGetTaskSignature was
  not handling fixed tasks, now it does not need to.

  KheDrsUpdateRerunCost was completely wrecked by the new code,
  because it happens when we call EvalSignature, but we are
  evaluating many signatures now.  I've fixed it so that on'
  a rerun we only evaluate the signatures we need.  It worked.

  If all choices have some cost, we can add the minimum in
  immediately and reduce the individual costs accordingly.
  I've implemented this (search for moved_cost) and it does
  happen sometimes.

  Fiddled with KheDynamicResourceSolverDoSolve; it's still quite
  messy, but it now does reruns when testing as well as when
  solving, as long as it finds a new best solution.

20 February 2022.

  I've rewritten the rerun code, including packaging it into a function,
  and making sure that testing variables only get set during main runs.
  It is now the way it ought to be.  A *rerun* is an open, search, and
  close carried out while drs->rerun != NULL.

  Even after tidying up reruns and testing, I've still got this bug:

  [ KheDrsRerun checking costs:
    open_and_search_cost 0.00000, monitor cost 0.00010 (TimeOff-TR_28-s10/TR_28)
    open_and_close_cost  0.00000, monitor cost 0.00010 (TimeOff-TR_28-s10/TR_28)
    open_and_search_cost 0.00000, monitor cost 0.00010 (TimeOff-TR_28-s10/TR_28)
    open_and_close_cost  0.00000, monitor cost 0.00010 (TimeOff-TR_28-s10/TR_28)
    open_and_search_cost 0.00000, monitor cost 0.00010 (TimeOff-TR_28-s10/TR_28)
    open_and_close_cost  0.00000, monitor cost 0.00010 (TimeOff-TR_28-s10/TR_28)
    open_and_search_cost 0.00000, monitor cost 0.00010 (TimeOff-TR_28-s10/TR_28)
    open_and_close_cost  0.00000, monitor cost 0.00010 (TimeOff-TR_28-s10/TR_28)
  ]

  But the final cost seems fine, so what's going on?  And why does the
  same constraint appear multiple times?  Probably because it has been
  broken up into several INT_SUM_COST expressions.

  Ahah - there are four times each day, and this monitor monitors one
  day and one time, making five monitored times altogether.  And guess
  what, only four open_and_search reports and four open_and_close
  reports appear above.  So the fifth time is supplying the cost 10
  and not being printed.  Can I do something so that this is fixed up?

  Yes, I've rewritten the checking code to sum the costs in adjacent
  expressions with the same monitor.  This required a new abstract
  supertype KHE_DRS_EXPR_COST.  All done and worked first time.

  Just finished a successful run with 4 resources over 2 weeks that
  reduced cost from 0.02505 to 0.02290.

  [ KheDrsSolveSearch(4 resources, 14 days)
    KheDrsSolveSearch ending day 1Mon (621 solns made, 621 kept)
    KheDrsSolveSearch ending day 1Tue (18120 solns made, 2784 kept)
    KheDrsSolveSearch ending day 1Wed (10692 solns made, 5160 kept)
    KheDrsSolveSearch ending day 1Thu (42152 solns made, 10695 kept)
    KheDrsSolveSearch ending day 1Fri (700346 solns made, 128714 kept)
    KheDrsSolveSearch ending day 1Sat (7347831 solns made, 762988 kept)
    KheDrsSolveSearch ending day 1Sun (10420677 solns made, 885007 kept)
    KheDrsSolveSearch ending day 2Mon (23294928 solns made, 482384 kept)
    KheDrsSolveSearch ending day 2Tue (25469934 solns made, 1400088 kept)
    KheDrsSolveSearch ending day 2Wed (87950480 solns made, 2030602 kept)
    KheDrsSolveSearch ending day 2Thu (105077242 solns made, 1591139 kept)
    KheDrsSolveSearch ending day 2Fri (2952665 solns made, 134565 kept)
    KheDrsSolveSearch ending day 2Sat (2372636 solns made, 104281 kept)
    KheDrsSolveSearch ending day 2Sun (9668 solns made, 1 kept)
  ] KheDrsSolveSearch returning true

  Sadly, it took 17.4 mins (no wonder, on 2Wed 2,030,602 undominated
  solutions were kept) and the final solution had cost 0.02000, so
  someone else was able to improve the solution further.  Incidentally,
  KHE20x8 found a solution of cost 1890 on this instance.

21 February 2022.  Working on revised dominance testing.  I've written
  a new section of the User's Guide explaining it.  We don't actually
  need an enumerated type of dom test types.  All tests are the same,
  it is just the a and b values that turn on or off the <= and >= parts.
  These values have to be set when expressions are opened.

22 February 2022.  Working on revised dominance testing.  I've audited
  what I wrote yesterday, and fiddled with it a bit.  It all seems good.
  Then went on to prove reflexivity and transitivity.  That seems good
  too, although it will need an audit.

23 February 2022.  Working on revised dominance testing.  Audited
  reflexivity and transitivity and managed to simplify it a bit,
  all good.

24 February 2022.  Working on revised dominance testing, specifically
  dominating and dominated sets.  Sorted out the effect of constant
  c, and audited what I've written about it.  Also audited dominating.

25 February 2022.  Working on revised dominance testing.  Audited
  dominating sets again and made some more changes, hopefully final,
  to do with requiring all signature values to be non-negative.
  Started again on dominated sets, have done the Z = false case so far.

26 February 2022.  Working on revised dominance testing.  Auditing
  yesterday's effort.

27 February 2022.  Audited dominating and dominated again.  I now have
  something that seems to be close to final.

28 February 2022.  Starting to implement, crash through or crash.
  Had to abandon all the tradeoff code for the time being, also I
  have skipped over

    HaArrayAddLast(e->sig_indexes, KheDrsDayAddDomTest(day, e->dom_test))

  for the moment.  But at least I have a clean compile.

1 March 2022.  Polished off a few small things in the implementation,
  left over from yesterday.  The two things left to do are to build
  dominance tests while opening, and to get tradeoff dominance back.

  Currently working on the dominance tests sub-section of A.15.8.
  This is where we calculate a and b, which are basically all that
  is needed to build dominance tests during opening.  Finished it;
  it needs and audit, then implement it in KheDrsExprOpen.

  I've written KheDrsExprDomTest, and I've written skeletons for its
  branches, and filled in one branch (INT_SUM_COST).  Now I need to
  fill in the rest.

4 March 2022.  Something funny about my dates, but this one is right.
  Audited the documentation, and got it into good shape, good enough
  to allow it to replace the old version.

5 March 2022.  Finished implementing the new dominance tests, ready
  to audit and implement, except not tradeoff dominance yet.  I seem
  to have confused a and b, so I've changed their names in the
  implementation to a and b.

  I commented out the old documentation and audited the new
  documentation to ensure that it covers everything properly.

  Four weeks to paper deadline.  Real soon now I will need to go
  with what I have.

6 March 2022.  Finished auditing the new dominance tests, all done
  and ready to test (except not tradeoff dominance yet).

  Fixed a stupid blunder in KheDrsConstraintCouldHaveCost.  Code
  is now running and finding new best solutions.

7 March 2022.  Here are dominating and dominated for the complete
  weekends constraint on 1Sat.  The determinant is 1 if 1Sat is
  busy and 0 otherwise.

    [ dom_test for Constraint:5/HN_1 on day 1Sat:
      [ [Z true, a 0, b 2]
	dominating(0) = {0, 2 .. 0}
	dominating(1) = {1 .. 1}
	dominating(2) = {2 .. 2}
	dominated(0) = {0 .. 0}
	dominated(1) = {1 .. 1}
	dominated(2) = {2 .. inf}
      ]
    ]

  This is fine, we basically have equality here.  I've looked
  through some other tests too, they all seem to be correct.

  Decided to bury pc in the trie data structure.  I've done the
  changes to the trie structure, and I've added HaArrayShiftRight
  and HaArrayShiftLeft to the ha module and documented them.

8 March 2022.  Worked right through the documentation, all good now.

9 March 2022.  Audited varying-base indexing, had to fix a little
  bug with inserting into an empty array.  All good now.  Also
  audited the documentation and added a little more context.

11 March 2022.  Testing again today.  All good, but 4 resources is slow:

    KheDrsSolveSearch ending day 1Mon (621 solns made, 621 kept)
    KheDrsSolveSearch ending day 1Tue (18120 solns made, 3456 kept)
    KheDrsSolveSearch ending day 1Wed (13176 solns made, 4218 kept)
    KheDrsSolveSearch ending day 1Thu (33908 solns made, 9565 kept)
    KheDrsSolveSearch ending day 1Fri (627958 solns made, 86997 kept)
    KheDrsSolveSearch ending day 1Sat (4561743 solns made, 478394 kept)
    KheDrsSolveSearch ending day 1Sun (6032240 solns made, 764537 kept)
    KheDrsSolveSearch ending day 2Mon (18,287,692 solns made, 885,264 kept)
    KheDrsSolveSearch ending day 2Tue (45,326,027 solns made, 3,262,146 kept)

  Started work on KheDrsDomTestDominatesWithTradeoff and finished it.

12 March 2022.  Audited what I've done so far on tradeoff dominance, and
  carried on with it.  Made a new section of the documentation for
  tradeoff dominance, and filled it with good stuff.  Brought back all
  the code except not triet yet - but I've documented a plan for that.

  Started work on the paper - hurrah!

14 March 2022.  Not sure about yesterday; today went on gardening.
  But did some work on the paper.

15 March 2022.  Still working on the paper.  Going well.

16 March 2022.  Still working on the paper.  Going well.

17 March 2022.  Still working on the paper.  Going well.  Got right
  to the end, in fact, although Experiments and Conclusion still to do

18 March 2022.  Still working on the paper.  Also coded trie tradoff
  dominance.  It's probably OK but it needs a careful top-down audit.

19 March 2022.  Auditing the trie tradeoff code.  Merged non-tradeoff
  and tradeoff throughout the solver by means of a with_tradeoff flag.

20 March 2022.  Auditing the trie tradeoff code, again.

21 March 2022.  Finished auditing the trie tradeoff code, again.
  Submitted the two old papers to PATAT 2022 today.  Started
  testing.

22 March 2022.  Generated a complete paper, suitable for submission,
  but I will try to improve it further before I do that.  I've saved
  it in best_dynamic.tex and best_dynamic.pdf, and saved them on my
  web site in patat22_archive.  Ten days to deadline.

23 March 2022.  Wrote code to print the rank of each solution along
  a successful path.  Got this on a 3 resource solve:

   Day        Rank      Total      %        Viable      Total      %
  ------------------------------------------------------------------
  2Sun           1          1  100.0             1          1  100.0
  2Sat           2        819    0.2           165        819   20.1
  2Fri           8       5477    0.1          1669       5477   30.5
  2Thu           3      22965    0.0          7044      22965   30.7
  2Wed          14      19361    0.1          7514      19361   38.8
  2Tue          11      25144    0.0          5936      25144   23.6
  2Mon           4      15442    0.0          6918      15442   44.8
  1Sun           5       6632    0.1          4907       6632   74.0
  1Sat           2        924    0.2           924        924  100.0
  1Fri          15        293    5.1           293        293  100.0
  1Thu           7         56   12.5            56         56  100.0
  1Wed           1         64    1.6            64         64  100.0
  1Tue           1         77    1.3            77         77  100.0
  1Mon          36        125   28.8           125        125  100.0
  ------------------------------------------------------------------
  Tot.         110      97380    0.1         35693      97380   36.7

  Rank means how far along the sorted list the ancestor of the best
  solution appears.  At the largest, 12.5% of the way along.  Viable
  means how many of the solutions have a cost that is not greater
  than the cost of the final solution, and thus would not be skipped
  over by an A* search.  It's not an encouraging result overall:
  even if non-viable solutions could be skipped at no cost, the
  running time might only reduce to 36% of what it is now.  But
  if we favoured extensions on later days we might do better.
  Needs thinking about.  Here is a four trainee nurse table:

   Day        Rank      Total      %        Viable      Total      %
  ------------------------------------------------------------------
  2Sun           1          1  100.0             1          1  100.0
  2Sat          21      51336    0.0          9308      51336   18.1
  2Fri           1     140989    0.0         32710     140989   23.2
  2Thu           1    2442839    0.0        446409    2442839   18.3
  2Wed          25    2486741    0.0        527858    2486741   21.2
  2Tue          13    1297309    0.0        279103    1297309   21.5
  2Mon          74     279943    0.0         93527     279943   33.4
  1Sun          33     363110    0.0        153679     363110   42.3
  1Sat          35     279475    0.0        199137     279475   71.3
  1Fri         312      47140    0.7         45665      47140   96.9
  1Thu         176       5281    3.3          5280       5281  100.0
  1Wed          91       2400    3.8          2400       2400  100.0
  1Tue         367       2448   15.0          2448       2448  100.0
  1Mon         174        621   28.0           621        621  100.0
  ------------------------------------------------------------------
  Tot.        1324    7399633    0.0       1798146    7399633   24.3

  Viables are much the same, rank is lower: pretty amazing that of
  2,486,741 undominated solutions the optimal one was ranked 25.

24 March 2022.  Decided to add a strong cache to triet.  Did not do
  the full run; the number of kept solutions was almost the same.

25 March 2022.  Had most of the day off.  Printing solutions grouped
  by cost, to see what the spread is.  There is a fair spread, nothing
  unexpected really.

  This is 3 trainee nurses using triet:

   Day        Rank      Total      %        Viable      Total      %
  ------------------------------------------------------------------
  2Sun           1          1  100.0             1          1  100.0
  2Sat           2        819    0.2           165        819   20.1
  2Fri           8       5477    0.1          1669       5477   30.5
  2Thu           3      22965    0.0          7044      22965   30.7
  2Wed          14      19361    0.1          7514      19361   38.8
  2Tue          11      25144    0.0          5936      25144   23.6
  2Mon           4      15442    0.0          6918      15442   44.8
  1Sun           5       6632    0.1          4907       6632   74.0
  1Sat           2        924    0.2           924        924  100.0
  1Fri          15        293    5.1           293        293  100.0
  1Thu           7         56   12.5            56         56  100.0
  1Wed           1         64    1.6            64         64  100.0
  1Tue           1         77    1.3            77         77  100.0
  1Mon          36        125   28.8           125        125  100.0
  ------------------------------------------------------------------
  Tot.         110      97380    0.1         35693      97380   36.7

  This is the same 3 trainee nurses using strongt:

   Day        Rank      Total      %        Viable      Total      %
  ------------------------------------------------------------------
  2Sun           1          1  100.0             1          1  100.0
  2Sat           2        218    0.9            86        218   39.4
  2Fri           6       1275    0.5           992       1275   77.8
  2Thu           2       4034    0.0          2828       4034   70.1
  2Wed           6       3103    0.2          2548       3103   82.1
  2Tue          12       3929    0.3          2108       3929   53.7
  2Mon           3       2271    0.1          1860       2271   81.9
  1Sun           4       1823    0.2          1768       1823   97.0
  1Sat           2        345    0.6           345        345  100.0
  1Fri          18        202    8.9           202        202  100.0
  1Thu          11         48   22.9            48         48  100.0
  1Wed          11         56   19.6            56         56  100.0
  1Tue          11         64   17.2            64         64  100.0
  1Mon          65        125   52.0           125        125  100.0
  ------------------------------------------------------------------
  Tot.         154      17494    0.9         13031      17494   74.5

  There are many fewer undominated solutions, in fact about 80% fewer.
  So triet is not doing as well as we need it to.

26 March 2022.  Thinking generally about how to improve things.
  Working on indexedt.  All done, ready for an audit and test.
  Also improved abstraction of soln list along the way.

27 March 2022.  Got indexedt working.  It does indeed have the
  smaller number of undominated solutions that we were expecting.
  The running time is about the same as triet.  This is curious:
  it's faster because it finds all cases of tradeoff dominance,
  and it's slower because the indexing is less efficient, and
  the two effects pretty well cancel each other out.

  Found a small glitch where hard costs were causing the increment
  to be 1 when it really should be 5.  All fixed now.

28 March 2022.  Got KheDrsSolnDominatesDebug working.  Looked carefully
  through an example, it all seems to be in order.  Most of the tradeoff
  cost is going on consecutive working days and consecutive same shift
  working days, quite reasonably as it seems to me:
  
  [ KheDrsSolnDominatesDebug(soln1 0.02300, soln2 0.02450)
      Min   Max   Weight | Soln1 Soln2 | Dominate | Monitor                    Description
    ---------------------+-------------+----------+---------------------------------------------
        7    15  0.00020 |    15    15 |  0.02300 | Constraint:12/TR_25        Min/Max shifts
        2     5  0.00015 |     0     0 |  0.02300 | Constraint:14/TR_25        Consec early
        2     5  0.00015 |     3     3 |  0.02300 | Constraint:16/TR_25        Consec afternoon
        3     5  0.00015 |     0     0 |  0.02300 | Constraint:17/TR_25        Consec night
        3     5  0.00030 |     3     3 |  0.02300 | Constraint:21/TR_25        Consec busy days
        2     4  0.00030 |     0     0 |  0.02300 | Constraint:18/TR_25        Consec free days
       2Z        0.00030 |     1     1 |  0.02300 | Constraint:6/TR_25         Complete weekends
                         |     1     1 |  0.02300 | 
              2  0.00030 |     2     1 |  0.02330 | Constraint:10/TR_25        Busy weekends
        2        0.00015 |     0     0 |  0.02330 | Constraint:15/TR_25        Consec morning
              1  1.00000 |     1     1 |  0.02330 | Constraint:2/TR_25/2Sat3   Pattern
              1  1.00000 |     0     0 |  0.02330 | Constraint:3/TR_25/2Sat4   Pattern
              1  1.00000 |     0     0 |  0.02330 | Constraint:1/TR_25/48      Pattern
        7    15  0.00020 |    15    15 |  0.02330 | Constraint:12/TR_26
        2     5  0.00015 |     0     0 |  0.02330 | Constraint:14/TR_26
        2     5  0.00015 |     0     0 |  0.02330 | Constraint:16/TR_26
        3     5  0.00015 |     0     0 |  0.02330 | Constraint:17/TR_26
        3     5  0.00030 |     1     3 |  0.02390 | Constraint:21/TR_26        Consec busy days
        2     4  0.00030 |     0     0 |  0.02390 | Constraint:18/TR_26
       2Z        0.00030 |     1     1 |  0.02390 | Constraint:6/TR_26
                         |     1     1 |  0.02390 | 
              2  0.00030 |     1     2 |  0.02390 | Constraint:10/TR_26
        2        0.00015 |     1     2 |  0.02405 | Constraint:15/TR_26        Consec morning
              1  1.00000 |     0     0 |  0.02405 | Constraint:2/TR_26/2Sat3
              1  1.00000 |     0     0 |  0.02405 | Constraint:3/TR_26/2Sat4
              1  1.00000 |     1     1 |  0.02405 | Constraint:1/TR_26/48
       15    22  0.00020 |    17    17 |  0.02405 | Constraint:13/TR_28
        2     5  0.00015 |     0     0 |  0.02405 | Constraint:14/TR_28
        2     5  0.00015 |     0     1 |  0.02420 | Constraint:16/TR_28        Consec afternoon
        3     5  0.00015 |     0     0 |  0.02420 | Constraint:17/TR_28
        3     5  0.00030 |     0     1 |  0.02450 | Constraint:23/TR_28        Consec busy days
        2     3  0.00030 |     1     0 |  0.02450 | Constraint:20/TR_28        Consec free days
       2Z        0.00030 |     0     1 | XXXXXXXX | Constraint:6/TR_28
                         |     0     1 | XXXXXXXX | 
              2  0.00030 |     1     1 | XXXXXXXX | Constraint:10/TR_28
        2        0.00015 |     0     0 | XXXXXXXX | Constraint:15/TR_28
              1  1.00000 |     0     1 | XXXXXXXX | Constraint:2/TR_28/2Sat3
              1  1.00000 |     0     0 | XXXXXXXX | Constraint:3/TR_28/2Sat4
              1  1.00000 |     0     0 | XXXXXXXX | Constraint:1/TR_28/48
    ---------------------+-------------+----------+---------------------------------------------
  ] false

  I didn't get any wonderful ideas while poring over the data.

29 March 2022.  Still no wonderful ideas.

30 March 2022.  Still no wonderful ideas.

31 March 2022.  Finalizing my submission (now labelled "work in
  progress") today.  Off-site backup, in fact new Version 2.7.  And
  I submitted the paper, one day before the deadline.  Hurrah.

3 April 2022.  I've been thinking about dominance testing for several days
  now without getting anywhere.  So today I've implemented the priority
  queue of solutions.  All done and documented, ready for an audit and test.
  Also started reorganizing KheDrsSolnSetPart into an inheritance structure.

4 April 2022.  Finishing off reorganizing KheDrsSolnSetPart into an
  inheritance structure.  Have clean compile, also sorted out issues
  around inserting into and deleting from the priority queue.

5 April 2022.  Sorted out KheDrsSolnSetPartDomMediumMoveAll and in
  fact all the MoveAll functions.  Much clearer now.
  
  Using "MeldSoln" to add a soln with dominance testing, and "AddSoln"
  to add a solution without (that is, after) dominance testing.

  General audit of the changes, all looking good, ready to test.

6 April 2022.  Currently testing priqueue.  Found and fixed a couple of
  little bugs, now KheDrsDistanceToAncestor is mysteriously crashing.

7 April 2022.  Sorted out what to do when expanded solutions are
  subsequently found to be dominated (don't delete or free them).
  Testing seems to have gone well.  It shows the priority queue
  as finding only marginally fewer solutions and hence running
  not much faster, or indeed a bit slower, presumably because
  of the extra overhead.

8 April 2022.  Can we do anything with consecutive busy days and
  consecutive free days?  The next assignment must add 1 to exactly
  one of these two determinants, but not both.  Does that influence
  our tradeoff calculations?  Not easy to see how.  It's true that
  we can partition the extensions into two sets, one for which the
  next day is a busy day, the other for which it is a free day,
  and get more detailed information about costs than we could get
  otherwise.  But one side will be expensive for the (hopefully)
  dominating solution, so we don't really get anywhere.
  
  Can we get better lower bounds?  For example, from the event
  resource constraints on each day?  Perhaps there is an
  unfillable nurse shortage that will add cost, whatever we do?
  If so, we can add this at the start and get better pruning.
  Not very likely and not easy to implement, so abandon this.

  Can we find every case of tradeoff dominance efficiently?  Perhaps
  there is an upper limit beyond which there is no point searching.
  No there isn't; I've checked, and it does not work that way.

  Can we optimize the interval calculations, perhaps by making
  functions that carry out the equivalent of several calls to the
  existing functions?  Does it matter, now that it is calculated
  once for each task class, not once for each combination?  No; it
  probably does not matter.  We really need optimizations that reduce
  the number of solutions made or kept.

  Working on replacing soln_limit by daily_soln_limit.  Boilerplate
  all done, now I just need to implement the new functionality,
  based on consulting drs->daily_soln_limit at the right moment.

  Done some fairly massive rewriting to reduce the number of
  operations on each solution container type.  Containers for
  solutions now begin with these operations:

      ContainerMake            - make a new container
      ContainerFree            - free container but not its contents
      ContainerCount           - number of elements
      ContainerGather          - gather contents into soln_list
      ContainerDominates       - true if container dominates soln
      ContainerRemoveDominated - remove and free dominated solns
      ContainerAddSoln         - add an element, without dominance

    Any other operations come after these ones.  The last two
    operations keep the priqueue up to date.

    Have clean compile, also did a check that the priority queue
    is still being used correctly.  It all seems good.

    Now freeing all solutions, solution lists, and solution sets
    at the end of the search.  This way I can do it differently
    depending on whether the priqueue was used or not:

      * After an ordinary search, all day->soln_set fields are NULL
        and have been handled correctly.  All solutions are in
	day->soln_list fields, plus root_soln and root_soln_list.

      * After a priqueue search, all day->soln_list fields are
        NULL, and all solutions are in day->soln_set fields,
	plus root_soln.

    Need to audit this.

9 April 2022.  Audited yesterday's code and tidied it up a bit.
  And tested it, it seems to be working fine.  Then I went on
  and brought the documentation up to date.

  Did a run with daily limit 10,000 and 4 resources.  It all seemed
  to work but it was still surprisingly slow:  the whole run took
  11 minutes.  This was owing to the huge number of solutions made
  each day.  Reducing the maximum to 1000 caused the run to finish
  in 79 seconds, which is a lot better.  Let's see what happens with
  the optimization I've just dreamt up, where we can prune generate
  paths which lead to costs over C.

  Started work on the new optimization.  I'm already sorting the
  choices for each resource by increasing cost.  That alone seems
  to be working, although curiously the solve ran more slowly on
  the 4 resource case (91 seconds rather than 79).

10 April 2022.  Carrying on with the new optimization.  Expanded
  the KHE_DRS_SIGNATURE/KHE_DRS_SOLN hierarchy into three parts:
  KHE_DRS_SIGNATURE/KHE_DRS_SOLN+KHE_DRS_RESOURCE_SIGNATURE, since
  it was not strictly type-safe as it was.

  Simplified KheDrsSolnDoExpand by not treating the two single
  choices as special cases.  Audited and tested, all good.

  Set rs_group_by_rc_off=true.  Running time increased from
  11 seconds to 18 seconds.  So much for it being faster.
  Of course, it could have been other parts of the solve
  running more slowly.

  Started work on the new optimization.  At present there are
  no reassignments of day->solve_prune_cost after its initial
  assignment to drs->solve_init_cost, so nothing has changed.
  But we can reduce it at any time to whatever we want, and
  then solutions for that day which exceed that cost will not
  be generated.

11 April 2022.  Documented daily_expand_limit and daily_prune_trigger,
  now I have to implement them (although daily_expand_limit is
  just daily_soln_limit renamed).

12 April 2022.  Implemented daily_expand_limit and daily_prune_trigger.
  All done and tested.  With both limits at 1000, four trainees are
  taking about 25 seconds, which is a lot better, but not fast enough.
  With both limits at 500, we get it down to 8 seconds, and we are
  still finding an improved solution.  Five trainee resources with
  the same limits takes 105 seconds but does find a new optimal.

15 April 2022.  Took a couple of days off to referee for PATAT.
  Today I prepared the algorithm (KheTaskingRepairResources in
  file khe_sr_combined.c) for solving with the VLSN search.

16 April 2022.  Thinking about making resource assignment more
  configurable, in order of steps and in time allocated:

    KheTaskingAssignResourcesStage1
      KheClusterMinimumSolverMake (etc)
      KheGroupByResourceConstraints
      KheMostConstrainedFirst or KheResourcePack or TimeSweep  rmc, rpk, rts
      KheTaskingRepairResources
      KheTaskSetUnGroup
      KheSetMonitorMultipliers

    KheTaskingAssignResourcesStage3
      KheTaskingRepairResources

    KheTaskingRepairResources
      KheResourceRematch			rrm
      KheEjectionChainRepairResources		rej
      KheDynamicResourceVLSNSolve		rvd

   We can use separate options for the smaller steps, like
   task grouping and monitor multipliers.

      alg ::= step { "," step } | item [ "(" alg ")" ]
      item ::= label [ ":" time_weight ]

   Then what we do now is

      rg(rt(rts,rrm,rej,rvd),rt(rrm,rej,rvd)

    Started work on parsing the new rs option at the end of
    file khe_sr_combined.c.

17 April 2022.  Wrote all the new code for reading the
  rs option and running accordingly.

18 April 2022.  Got it running, including some debug output to show
  where things are up to.  Did a longer run (15 minutes requested):

    [ "INRC2-4-030-1-6291", 1 solution, in 16.1 mins: cost 0.02095 ]
  
  Is something wrong?  Got this:

  [ KheDrsRerun checking costs:
    open_and_search_cost 0.00080, monitor cost 0.00010 (TimeOff-HN_3-s10/HN_3)
    open_and_close_cost  0.00080, monitor cost 0.00010 (TimeOff-HN_3-s10/HN_3)
    open_and_search_cost 0.00260, monitor cost 0.00020 (TimeOff-NU_10-s10/NU_10)
    open_and_close_cost  0.00260, monitor cost 0.00020 (TimeOff-NU_10-s10/NU_10)
    open_and_search_cost 0.00040, monitor cost 0.00010 (TimeOff-NU_13-s10/NU_13)
    open_and_close_cost  0.00040, monitor cost 0.00010 (TimeOff-NU_13-s10/NU_13)
    open_and_search_cost 0.00080, monitor cost 0.00010 (TimeOff-CT_24-s10/CT_24)
    open_and_close_cost  0.00080, monitor cost 0.00010 (TimeOff-CT_24-s10/CT_24)
  ]

  Needs looking into.  The open and search costs are impossibly large,
  beyond what the monitor could ever produce.  But I seem to remember
  that there was some issue concerned with breaking the expression
  into multiple expressions?  Several of them are off only on days
  that are not opened by the solve anyway.  Are the values being
  cleared at the end of each run?  They need to be.

  The open_and_search_cost is initialized to the monitor cost.  It is
  not initialized to the expression's *share* of the monitor cost.
  This is very likely the source of the problem.

19 April 2022.  Fixing the open_and_search_cost bug today, by having
  a single open_and_search_cost for each monitor.  I've written all
  the code, based around a new KHE_DRS_MONITOR_INFO type.  It's pretty
  good, I have a clean compile, audited, and tested and it seems to
  be working.

20 April 2022.  Testing is going on steadily.  Extend all:x to all:x:y
  where y gives you an increment, then tried all:14:7.  It feels better
  than random:14, because that can start or end near the boundary, which
  is not a great idea.

  In KHE20 paper, KHE20 took 5.6 seconds to produce a cost of
  1980.  KHE20x8 took 26.5 seconds to produce a cost of 1835.
  We're currently taking 15.2 mins to produce cost 0.02065.

21 April 2022.  Best of 8, running 15 minutes per solve (4 resources, 14 days):

    [ "INRC2-4-030-1-6291", 4 threads, 8 solves, 6 distinct costs, 30.0 mins:
      0.01875 0.01910 0.01915 0.01950 0.01950 0.01950 0.01995 0.02055
    ]

  Here we are without dynamic programming, all else the same:

    [ "INRC2-4-030-1-6291", 4 threads, 8 solves, 8 distinct costs, 19.7 secs:
      0.01885 0.01930 0.01950 0.01960 0.01970 0.02000 0.02015 0.02090
    ]

  So every solution is better for using dynamic programming, but not
  much better.

  Here we are calling ejection chains twice:

    [ "INRC2-4-030-1-6291", 4 threads, 8 solves, 8 distinct costs, 25.8 secs:
      0.01885 0.01890 0.01940 0.01960 0.01970 0.01985 0.02000 0.02040
    ]

  There is some improvement but not to the minimum.  Three times:

    [ "INRC2-4-030-1-6291", 4 threads, 8 solves, 8 distinct costs, 28.4 secs:
      0.01885 0.01890 0.01940 0.01960 0.01970 0.01985 0.02000 0.02030
    ]

  Just one small improvement, to the most expensive solution.  Best
  of 32, ejection chains only:

    [ "INRC2-4-030-1-6291", 4 threads, 32 solves, 23 distinct costs, 100.7 secs:
      0.01885 0.01890 0.01895 0.01905 0.01920 0.01940 0.01940 0.01945
      0.01945 0.01945 0.01960 0.01960 0.01960 0.01965 0.01970 0.01975
      0.01985 0.01995 0.01995 0.01995 0.02000 0.02005 0.02010 0.02025
      0.02025 0.02030 0.02040 0.02040 0.02045 0.02050 0.02075 0.02080
    ]

  No further improvement.  My best previous result was 1835.  Why the
  decline, I wonder?

  Written all the resource_expand_limit code in both source files,
  and documented it.  Ready to audit and test.

22 April 2022.  Auditing and testing resource_expand_limit today.
  Seems to be working as expected.  Did a fairly long run with
  5 resources, it found one reduction (from 2045 to 2010), so it
  is working, just not wonderfully well.  Here is best of 8:

  [ "INRC2-4-030-1-6291", 4 threads, 8 solves, 8 distinct costs, 30.8 mins:
    0.01940 0.01960 0.01965 0.01970 0.01995 0.02010 0.02015 0.02080
  ]

  Not wonderful.  And here is best of 8, the old way:

  [ "INRC2-4-030-1-6291", 4 threads, 8 solves, 7 distinct costs, 20.6 secs:
    0.01880 0.01905 0.01950 0.02000 0.02030 0.02045 0.02060 0.02060
  ]

  This is quite a lot better, but still not as good as the result of
  1835 from my own paper.  It's faster than that result too, why?

23 April 2022.  I've been pondering where to go from here, and I'm
  still pondering.

  I've come up with an interesting idea that I call internal
  dominance.  Given distinct resources r1 and r2 and distinct
  task classes c1 and c2, it may be possible to show that
  every solution derived from a given solution S which assigns
  r1 to c1 and r2 to c2 is dominated by the corresponding
  solution derived from S which assigns r2 to c1 and r1 to c2.
  In that case, as we generate the solutions derived from S,
  on each path that assigns r1 to c1 we can mark r2 as being
  not assignable to c2, since going on to assign r2 to c2 would
  produce a solution known to be dominated by something else.

24 April 2022.  Documented internal dominance today.

25 April 2022.  Auditing internal dominance documentation.

27 April 2022.  Finished the internal dominance documentation.  It
  needs an audit, then I can start implementing.

28 April 2022.  Audited the internal dominance documentation.  All
  good.  Started implementing, changed KHE_DRS_RESOURCE_SIGNATURE
  to KHE_DRS_ASSIGNMENT to begin with, and did some tidying up
  which basically enhances the status of KHE_DRS_ASSIGNMENT.

  Reorganized the implementation and documentation to follow
  the times, resources, events, constraints, solutions form.
  All done but the code order needs an audit, and then I
  need to put in forward references to make it compile again.

29 April 2022.  Audited reorganized submodules, got back to a clean
  compile.  Working on internal dominance, done a lot, including
  implementing and documenting KheTaskClassResourceDominates.

30 April 2022.  Added use_internal_dominance flag alongside
  use_priqueue flag, and used it to turn on internal dominance.
  Got a long way through internal dominance, including adding
  code to keep skip_count up to date and skip assignments if
  it is positive.  It all needs a careful audit, plus
  KheDrsAssignmentPairDominates is not taking event resource
  constraints into account yet.

1 May 2022.  Audited all the internal dominance code.  All good
  except event resource constraints still to do.

2 May 2022.  Internal dominance is now all written (hurrah!).
  It needs a careful audit, then a test.

3 May 2022.  Audited the internal dominance code.  Done and ready
  to test.  Also rewrote the task class stuff after realizing that
  favourable assignments could actually add to the available cost.

  Did some testing, including writing some debug code and some
  code for accessing drs testing via option rs_drs_test.  Got
  something going, it seems to show that for 3 resources it
  often finds about 16 dominated pairs:

    [ KheDrsSetUpInternalDominance(drs, 1Tue)
      internal dom <NU_6 : {1Tue:Early.5...(5)}> + <HN_1 : {1Tue:Early.10...(9)}>
      internal dom <NU_6 : {1Tue:Day.3...(5)}> + <HN_1 : {1Tue:Day.12...(7)}>
      internal dom <NU_6 : {1Tue:Late.3...(5)}> + <HN_1 : {1Tue:Late.8...(9)}>
      internal dom <NU_6 : {1Tue:Night.4...(6)}> + <HN_1 : {1Tue:Night.10...(11)}>
      internal dom <NU_9 : {1Tue:Early.5...(5)}> + <HN_1 : {1Tue:Early.10...(9)}>
      internal dom <NU_9 : {1Tue:Early.5...(5)}> + <HN_1 : (free)>
      internal dom <NU_9 : {1Tue:Early.10...(9)}> + <HN_1 : (free)>
      internal dom <NU_9 : {1Tue:Day.3...(5)}> + <HN_1 : {1Tue:Day.12...(7)}>
      internal dom <NU_9 : {1Tue:Late.3...(5)}> + <HN_1 : {1Tue:Late.8...(9)}>
      internal dom <NU_9 : {1Tue:Night.4...(6)}> + <HN_1 : {1Tue:Night.10...(11)}>
      internal dom <NU_9 : {1Tue:Early.5...(5)}> + <NU_6 : (free)>
      internal dom <NU_9 : {1Tue:Early.10...(9)}> + <NU_6 : (free)>
      internal dom <NU_9 : {1Tue:Early.5...(5)}> + <NU_6 : {1Tue:Early.10...(9)}>
      internal dom <NU_9 : {1Tue:Day.3...(5)}> + <NU_6 : {1Tue:Day.12...(7)}>
      internal dom <NU_9 : {1Tue:Late.3...(5)}> + <NU_6 : {1Tue:Late.8...(9)}>
      internal dom <NU_9 : {1Tue:Night.4...(6)}> + <NU_6 : {1Tue:Night.10...(11)}>
    ] KheDrsSetUpInternalDominance

  So far so good.  Making sure this is really correct will be a
  challenge.  NB assignments are printed wrong way round here, but that
  is a bug in the debug code, now fixed.

4 May 2022.  Wrote all code needed to produce a graph of solutions made,
  alongside table size and running time.  Got the graph for 3 resources
  and 4 resources: internal dominance made fewer solutions, but only very
  marginally fewer.  4 Nurses came in about 30% faster.  Kept numbers were
  identical, so the code is correct, in concept and implementation.

6 May 2022.  Wrote some doc about free day dominance, not complete yet.

7 May 2022.  Still thinking about free day dominance, not much time
  for anything today.

8 May 2022.  Still thinking about free day dominance.

10 May 2022.  Still thinking about free day dominance.

11 May 2022.  Trying to implement the new dominance ideas, as a way of
  driving the thing forward.  KheDrsTaskClassAddResourceDominates and
  KheDrsTaskClassSubResourceDominates are next.

  Terminology:

      Single assignment dominance
      Paired assignment dominance
      Whole-day dominance

12 May 2022.  Slogging through single dominance.  I spent today
  updating the documentation, explaining everything in detail.  It's
  in pretty good shape now.  KheDrsTaskClassAddResourceDominates and
  KheDrsTaskClassSubResourceDominates are next.

13 May 2022.  Slogging through single dominance.  Whole thing is
  written now and has a clean compile.  I've audited it but it
  needs another careful audit, focussing on the overall concept.

14 May 2022.  I've decided I need a section explaining how to
  prove tradeoff dominance, in a general setting.

15 May 2022.  Audited the new dominance documentation.  It's in
  good shape now.  I've even been able to connect the existing code
  (specifically KheDrsDomTestDominatesNew) to the new documentation.

16 May 2022.  Worked over the new dominance documentation, and
  decided how to proceed with it from here.

21 May 2022.  For the last few days I've been revising my three
  PATAT 2022 papers, which were accepted, and working on the
  overheads for my talks there.  I've now submitted all three
  revised versions to EasyChair, and I've put them and the
  overheads on my own web site.  Exception:  I have not created
  overheads for my nurse rostering paper yet.  Since I'm still
  working on it I might wait a month or two, and then make the
  overheads, including some updated results in them.

  Then went back to dominance testing and read over the new stuff.

22 May 2022.  Still writing the new dominance documentation.

24 May 2022.  Doing other stuff yesterday, including buying my
  PATAT plane ticket.  Back at work today.

25 May 2022.  Still writing the new dominance documentation.
  Had an interesting idea:  don't use algebra, just code it.

26 May 2022.  Still writing the new dominance documentation.
  Actually I seem to have reached the end of it.  I could
  implement now, but I suppose I should think it over first.

27 May 2022.  Still writing the new dominance documentation.

28 May 2022.  Still writing the new dominance documentation.
  Actually its looking like I need a major section entitled
  "Determinants, costs, and dominance checking" with sections

  Assignments, solutions, and extensions
    Content as now.

  Monitors and costs
    Content as now.
    L, U, Z, f, D, delta, etc., including cost in incomplete solutions

  Proving dominance
    Content as now, then carrying on to f(delta(d+e)) stuff.

30 May 2022.  Still writing the new dominance documentation.  The
  current issue is proving that cost is monotone non-decreasing as
  solutions become more complete.  I'm working on that.

31 May 2022.  Still writing the new dominance documentation.  I've
  just finished proving that cost is monotone non-decreasing as
  solutions become more complete.  I've also written a new
  proving dominance section.

1 June 2022.  Still writing the new dominance documentation.

2 June 2022.  Still writing the new dominance documentation.
  Audited everything, and solved the e problem by adding e
  to the cache index alongside d1 and d2.

3 June 2022.  I need to work out what to do about signature
  entries that represent the children of cost nodes.  Written
  a section about it, now I need to ponder.

4 June 2022.  Pondering the children of cost nodes.  All seems good,
  started implementing.  Wrote all the table construction code.

5 June 2022.  Rewrote the dom table debug code.  Started testing.
  Tidied up the table format, and fixed a problem whereby D was too
  large in Constraint:13 - I replaced the number of time groups by
  the number of children.  Also worked on a problem to do with e
  and x.  Changed a few formulas and got this, for example:

    [ e  = 25    d2 =  0  d2 =  1  d2 =  2  d2 =  3
	      +------------------------------------
      d1 =  0 |  0.00000 -0.00020 -0.00040 -0.00060
      d1 =  1 | -0.00020  0.00000 -0.00020 -0.00040
      d1 =  2 | -0.00040 -0.00020  0.00000 -0.00020
      d1 =  3 | -0.00060 -0.00040 -0.00020  0.00000
	      +------------------------------------
    ]

  which seems pretty good.  You lose whichever way you move
  away from equality, because the upper limit gets you one
  way and the lower limit gets you the other.

  Finding the maximum value in each of these tables would be
  interesting.  If it is 0 in every table, then once you go
  negative you can't get back again, and dominance can fail.

  This one is for Constraint:14 (min 2, max 5, weight 15, history 0):

    [ e  = 25    d2 =  0  d2 =  1  d2 =  2  d2 =  3
	      +------------------------------------
      d1 =  0 |  0.00000 -0.00015 -0.00015 -0.00015
      d1 =  1 | -0.00015  0.00000 -0.00015 -0.00015
      d1 =  2 | -0.00030 -0.00015  0.00000  0.00000
      d1 =  3 | -0.00045 -0.00030 -0.00015  0.00000
	      +------------------------------------
    ]

  Isn't this wrong?  Shouldn't (e = 25, d1 = 1, d2 = 3) be -30?  No,
  because we can't get up to 1 + 4 and 3 + 4, it's off the end.

6 June 2022.  Must remember, when looking at these tables, that
  they show the difference between two *extra costs*.  When we
  are way over the limit these extra costs will be the same and
  that will give a difference of 0.  The huge difference in
  cost will already have been reported to c(S1) and c(S2).

  Started work on implementing uniform dominance.  All the
  boilerplate of passing the option around is done.

7 June 2022.  Finished a proof that Delta is always <= 0.
  This will allow dominance tests to abort as soon as the
  available cost goes negative, and also allow indexed
  uniform dominance.  I've audited it and it's fine.

  Done the boilerplate for indexedu.

8 June 2022.  Implementing uniform dominance.  I've done the
  actual test now, so all done except initializing dom_test_sub.

9 June 2022.  Working over the documentation to include history
  as a first class citizen.

  Broken the appendix into two appendices, "Theory" and
  "Implementation".  Currently working through the theory
  appendix, unifying the terminology.  Up to "Running time"

12 June 2022.  Still working through the theory appendix.
  Going steadily, some good things happening.  Written the
  entire counter monitors section.  Might add something
  about calculating determinants of extensions, and also
  there is a check needed where a ci got added, but basically
  things are in good order.

13 June 2022.  Still working through the theory appendix.

14 June 2022.  Still working through the theory appendix.

15 June 2022.  Still working through the theory appendix.  The
  issue at the moment is proving that Delta <= 0.  I've done
  it, but it's complicated; it needs a careful audit.

16 June 2022.  Still working through the theory appendix.  Audited
  the Delta <= 0 proof, it's all good.  Added something about
  calculating determinants and costs of extensions.

17 June 2022.  Audited everything, ready to start sequence monitors.

18 June 2022.  Starting the sequence monitors section today.
  Written the definition and monitors-and-costs sections, by
  grabbing previous stuff and improving it.

19 June 2022.  Reviewed sequence monitor stuff, I'm ready now to
  start on uniform dominance.

21 June 2022.  Still working through uniform dominance for sequence
  monitors.

22 June 2022.  Still working through uniform dominance for sequence
  monitors.  Made a few really good preparatory definitions.

23 June 2022.  Still working through uniform dominance for sequence
  monitors.

24 June 2022.  Still working through uniform dominance for sequence
  monitors.

25 June 2022.  Still working through uniform dominance for sequence
  monitors.

26 June 2022.  Still working through uniform dominance for sequence
  monitors.  Had an interesting idea to introduce l(m, S) and
  u(m, S) as lower and upper bounds on the determinant.  It gives
  a more coherent presentation.  Done it for counter monitors except
  not yet for uniform dominance.

27 June 2022.  Carry out the new idea l(m, S) for uniform dominance of
    counter monitors.  I've done one of the four cases.

29 June 2022.  Carry out the new idea l(m, S) for uniform dominance of
    counter monitors.  Done a lot and now ready to tackle uniform
    dominance for sequence monitors.

30 June 2022.  Working on uniform dominance for sequence monitors.

1 July 2022.  Working on uniform dominance for sequence monitors.
  Progressing slowly but steadily.  I got to the cache expression
  today; sadly, it's more complicated than for counter monitors,
  so I have to think about that.  Can we give up exactness to get
  something similar which is simpler?

2 July 2022.  Not much done today.  Slowly pressing on.

3 July 2022.  Pressing on.  Did some rewriting and some clarifying.

4 July 2022.  Audited the theory chapter and tidied it up.  I've
  done just about all there is to do there.  So I need to gird my
  loins now and audit the implementation against it, and then
  implement the caching.  Also worked on my PATAT overheads today.

7 July 2022.  I've been working on the PATAT overheads, they're
  all done now.  Time to audit the code against the new docs.
  I'll do counter constraints first.

9 July 2022.  Struggling to get going on the audit.  Today.
  I've audited KHE_DRS_EXPR_INT_SUM_COST, and moved the dom
  table stuff so that it is for KHE_DRS_EXPR_INT_SUM_COST
  expressions only.  Also did offsetting in the dom table
  and made sure it was initialized properly.  So it's all done.

  In preparation for the sequence code I've audited what I've
  written about sequence monitors.  All good except that the
  uniform dominance section seems a bit inexact at times.  I
  need to pin it down to something completely concrete and
  thus implementable before I go on to the implementation.

10 July 2022.  Revised uniform dominance for sequence monitors.
  It's now in a very clear and concrete state, which is great.
  It needs an audit, then it will be ready for implementing.

11 July 2022.  Auditing uniform dominance for sequence monitors.
  All good and ready to implement.  Made a start, boilerplate done,
  KheDrsDomTableSeqPut done.

12 July 2022.  Implementing uniform dominance for sequence monitors.
  The code is all written.  It needs a careful audit, then it will
  be ready for testing.

13 July 2022.  Pondering the relationship between open_child_index
  and the current cut.  I've written a draft in the doc, it needs
  auditing, then implementing.

14 July 2022.  Auditing my documentation of the relationship between
  open_child_index and the current cut.

15 July 2022.  I've implemented KheDrsExprIntSeqCostDomTest and
  KheDrsAUIntervalFindRightLengths.  They need an audit.

16 July 2022.  Audited yesterday's stuff.  I ended up completely
  re-implementing KheDrsExprIntSeqCostDomTest.  Another audit needed.
  Also audited the documentation and moved some more stuff into the
  Theory chapter.

17 July 2022.  Audited all the new code for seq dominance testing.
  It's ready to run.

18 July 2022.  Auditing the implementation chapter.  All good down
  to the end of expressions in the source code.  Finished a new
  Dominance major category, all good.  Expressions good.  Now
  working through solutions and solving, still a bit of a mess.

19 July 2022.  Finished the signature value adjustment section for
  counter monitors, concluding that signature value adjustment is
  not much use to uniform dominance testing.  Removed all trace of
  KHE_DRS_EXPR_COST from the code.  The KHE_DRS_EXPR_INT_SUM_COST
  section of the doc is revised now, it's great.

20 July 2022.  I've bypassed KHE_DRS_EXPR_INT_SEQ_COST for the
  moment, and I'm working through Solutions (signatures,
  assignments, solutions).  Going well but rocks ahead.

21 July 2022.  Working on the Expansion section.  I've made a start.

22 July 2022.  Finished the Expansion section and audited the
  Packed solutions section.  This completes the Solutions subappendix.

23 July 2022.  Finished off the documentation revision, including
  KHE_DRS_EXPR_INT_SEQ_COST.

24 July 2022.  Started thinking about algebra for one-extra and
  two-extra selection.  Nothing written down yet.

24 July 2022.  Still thinking about algebra for one-extra and
  two-extra selection.  I generalized my introduction to uniform
  dominance somewhat, making it more relevant.

26 July 2022.  Still thinking about algebra for one-extra and
  two-extra selection.  Deleting clearly uncompetitive assignments
  as soon as they are created.  Added KheDrsAssignmentFree.

27 July 2022.  Still thinking about algebra for one-extra and
  two-extra selection.

28 July 2022.  Still thinking about algebra for one-extra and
  two-extra selection.  Working on p and q, but it has occurred
  to me that one end of the range might always be best because
  the tasks in each task class are sorted.  Lots to work out.

28 July 2022.  Still working on the algebra for one-extra and
  two-extra selection.  Finally starting to get somewhere.

30 July 2022.  Still working on the algebra for one-extra and
  two-extra selection.  I've just finished one-extra selection,
  but what I've written needs a very careful audit and pondering.

  I've looked quickly through the code.  There is one point
  where I seem to have blundered and made a max instead of
  a min, and another where I've assumed that unassign cost
  is the negative of assign cost.  So some rewriting to do,
  although the structure is correct.

31 July 2022.  Audited and expanded the algebra for one-extra
  selection.  It's ready now to be implemented, by adjusting
  the old implementation.

1 August 2022.  Audited the algebra for one-extra selection
  again, and made some changes to clarify things further.  I
  need to audit it one more time, then implement.

3 August 2022.  Bone marrow biopsy killed off yesterday.  Did
  one more audit, it really is ready to implement now.  I've
  done that and I have a clean compile, ready to audit.

4 August 2022.  KheDrsSetUpOneExtraSelection and everything it
  calls audited once again.  It's ready to test, but we'll go
  on and revise KheDrsSetUpTwoExtraSelection first.

5 August 2022.  Working on the algebra for two-extra selection.
  All done, started to implement (by generalizing the one-extra code).

6 August 2022.  Made an off-site backup today.  Did another audit
  of the two-extra documentation, it is brief but fine.  Wrote the
  code and removed now-unused code from the task classes file and
  documentation.

7 August 2022.  Done some reorganizing of the one-extra and two-extra
  dominance code:  I've moved it into its own submodule, and I've
  changed the parameter names to more closely follow the names used
  in the documentation.  It all seems very clear and correct.

8 August 2022.  Found some problems with selecting the right dominace
  test, so I've added a KHE_DRS_DOM_TEST_TYPE.

9 August 2022.  Carried out a careful audit of dominance testing,
  including replacing the body of KheDrsSolnDoDominates with
  KheDrsSignatureDominates.  All good.  Also wrote a very brief
  account of signature value adjustment for sequence monitors,
  which accords with what I've currently implemented.

  KheDrsOneExtraSelectionDominates and KheDrsTwoExtraSelectionDominates
  really only work for uniform dominance, because KheDrsTaskClassMinCost
  really only works for uniform dominance.  I've documented that
  and made sure that they don't try to do anything else.

10 August 2022.  Kidney biopsy took out yesterday.  In KheTaskDoMinCost,
  I've improved the handling of step functions.

11 August 2022.  Constructed a list of notations used in the theory
  appendix.  It more or less confirms that the only clash is m, which
  stands for both the number of resources and a monitor.

  Considered using x and y for monitor names throughout the theory
  appendix, rather than m, because m is already in use for the number
  of open resources.  I've more or less decided against this, because
  it would be a big job of doubtful value.

12 August 2022.  Audited one-extra and two-extra selection again.
  Found one bug, uncertainty over whether KheDrsSignatureDominates
  handled costs as well as signatures proper.  All fixed now and good.

  On 23 April 2022 I started work on internal dominance, now called
  one-extra and two-extra dominance.  That led to uniform dominance
  and a complete rewrite of the documentation.  Almost four months.

13 August 2022.  Started testing.  Fixed some bugs, now have a clean
  run through to the end.  Needs some careful testing.  There are no
  cases where a sum table entry is positive.

  Here is the first case of a positive available cost:

    KheDrsExprIntSeqCostSetDomTable(Constraint:14/HN_0)
    KheDrsDomTableSeqPut(dt, p 2, q 0, r 1, l1 0, l2 5, cost 0.00015)

  Mininum is 2, maximum is 5.  This seems to make sense, in that
  l2 + r will always incur a cost of 15, while l1 + r might not.
  But I should work through it in detail.

14 August 2022.  Verified that the case I discovered yesterday
  (see just above) really does give a positive available cost 
  change.  All these cases are sequence monitors, there are no
  sum monitor cases in the testing, which is a relief.  I've
  documented all this and written up a proof that the case r = 0
  leads to non-positive available costs.  Needs an audit.

15 August 2022.  Audited the new proof that the case r = 0
  for sequence monitors leads to non-positive available costs.
  Also did some restructuring to highlight these theorems.

16 August 2022.  Looking over debug prints of successful OneExtra
  dominance checks.  Fixed a bug with passing planned_dtc and
  planned_dtd.  It was correct originally but tidying up broke it.

17 August 2022.  Got an assert error:

    KheDrsDomTableSubSubGet(0x7eff1c39e280): l2 (5) out of range (0 .. 1)

  The indexes in the doc seem correct, and the initialization code
  seems to accurately reflect the doc, so this will need some
  serious looking into.  It may be a logic error in how l1 and
  l2 are defined or calculated.

    val2=5, val1=<optimised out>

  Adding some debug output now.  This call seems to be erroneous:

    KheDrsDomTestDominatesUniform(dt, 0, 5)

  in that I have printed all cases where a value of at least 5 is
  added to a signature, and there are none.

  "KheDrsOneExtraSelectionDominates(<NU_6 on 1Mon: ..." so the
  signature comparison should be between two NU_6 signatures,
  but we in fact get

    KheDrsDomTableSubSubGet(0x7f271e6fa4d0) about to crash
      offset = 0, HaArrayCount(dtss->indexed_by_l2) = 2
      monitor Constraint:14/HN_1

  which seems to be the wrong resource.  This is the problem:

    dt = HaArray(day->dom_tests, i);
    v1 = HaArray(sig1->sig, i);
    v2 = HaArray(sig2->sig, i);
    if( !KheDrsDomTestDominatesUniform(dt, v1, v2, uniform_avail_cost) )

  The problem is that the signatures relate to a single resource, but
  the day->dom_tests array contains tests for all resources.  I've
  made a start on fixing it and indeed done most of it.

18 August 2022.  Finished off the bug fix, getting a clean run
  through to the end now.  Looked at some OneExtra debug output,
  it's simple cases but they all seem to be fine.  Did a run with
  four resources, it is slow but better than it was:
  
    KheArchiveParallelSolve returning (20.6 mins elapsed)
    
  And it did find an improvement for four trainee nurses, which
  is a good indication that it is working correctly.

19 August 2022.  Made some debug output for two extra selection,
  it all seems fine.  Here are the sorted ranks of the best path
  on a 3-resources run that found a new best:

     Day        Rank      Total      %        Viable      Total      %
    ------------------------------------------------------------------
    2Sun           1          1  100.0             1          1  100.0
    2Sat           2        175    1.1            74        175   42.3
    2Fri           6        904    0.7           718        904   79.4
    2Thu           2       3181    0.1          2282       3181   71.7
    2Wed          15       2792    0.5          2316       2792   83.0
    2Tue          12       1175    1.0           989       1175   84.2
    2Mon           6       1846    0.3          1650       1846   89.4
    1Sun           4       1435    0.3          1435       1435  100.0
    1Sat           2        345    0.6           345        345  100.0
    1Fri          14        116   12.1           116        116  100.0
    1Thu           4         24   16.7            24         24  100.0
    1Wed          10         56   17.9            56         56  100.0
    1Tue          12         64   18.8            64         64  100.0
    1Mon          45        125   36.0           125        125  100.0
    ------------------------------------------------------------------
    Tot.         135      12239    1.1         10195      12239   83.3

  No issues with viable, the usual low numbers for sorted rank, although
  not on the very first day.  Presumably on the first day there are many
  equal best solutions.

22 August 2022.  Been fiddling around with the debug print for
  dominance tests, saw this interesting thing comparing best and worst:

  [ KheDrsSolnDominatesDebug(soln1 0.02280, soln2 0.02435)
      Res    Avail  Details
    ----------------------------------------------------------------
     true  0.00155  sig cost
     true  0.00155  sig[ 0] 15 15  cbtc 7-15|s20     Constraint:12/TR_25
     true  0.00155  sig[ 1]  0  0  laic 2-5|s15      Constraint:14/TR_25
     true  0.00155  sig[ 2]  2  0  laic 2-5|s15      Constraint:16/TR_25
     true  0.00155  sig[ 3]  0  0  laic 3-5|s15      Constraint:17/TR_25
     true  0.00125  sig[ 4]  2  3  laic 3-5|s30      Constraint:21/TR_25
     true  0.00125  sig[ 5]  0  0  laic 2-4|s30      Constraint:18/TR_25
     true  0.00095  sig[ 6]  2  1  cbtc 0-2|s30      Constraint:10/TR_25
     true  0.00080  sig[ 7]  0  2  laic 2-28|s15     Constraint:15/TR_25
    false  0.00080  sig[ 8]  1  0  cbtc 0-1|h1       Constraint:2/TR_25/2Fri3
    ----------------------------------------------------------------
  ]

  The second last line is correct, because the constraint is a limit
  active intervals constraint, so the 0 determinant costs nothing, and
  if the next time group is active, the numbers will be 1 and 3 and the
  1 is just one below the lower limit of 2, costing 15.

  On the last line, the unwanted pattern is also interesting.  It
  prevents dominance even though there is plenty of soft cost
  available.  This is because soln1 has to do something to
  avoid the unwanted pattern, but soln2 does not.  So soln2
  can try paths that are not open to soln1, hence no dominance.

23 August 2022.  Most of day lost to cancer appointment.  Thought
  about larger task classes.

26 August 2022.  In the last few days I have spent some time looking
  over XUTT and rehearsing my conference talks.

28 August 2022.  Starting to write up the latest idea, which I am
  calling "shift-based selection".

29 August 2022.  Working on shift-based selection.  Did an off-site
  backup.  I leave for PATAT 2022 later today.

9 September 2022.  I've been away at PATAT 2022, and for the last
  few days I've been settling back in.  Ready to work again today.
  Starting by reviewing what I've written about shift-based selection.

11 September 2022.  Started coding shift-based selection, somewhat
  tentatively.  So far I've written and tested the code for building
  shifts.  The next step would seem to be to build sets of undominated
  solutions indexed by a set of resources R.

12 September 2022.  Coming to grips with how the expansion will work
  when we go shift by shift.  Something like this:

    ShiftExpand(int shift_index, 
      set(resource) remaining_resources, soln S)
    {
      if( shift_index is off the end )
      {
        in S, assign a free day to each r in remaining_resources;
	Add S to new day, including dominance testing;
      }
      else
      {
	shift s = day->shifts[shift_index];
	SS = s.init_shift_structure;
	ShiftDoExpand(shift, shift_index, SS, remaining_resources, 0, 0, S);
      }
    }

    ShiftDoExpand(shift, shift_index, resources, resources_index,
      SS, unselected_resources, S)
    {
      if( resources_index is off end )
      {
	for each set of assignments X in SS
	  ShiftExpand(shift_index + 1, unselected_resources, S + X)
      }
      else
      {
	r = resources[resources_index];
	if( SS.trywith(r, &SS2) )
	{
	  ShiftDoExpand(shift, shift_index, resources, resources_index + 1,
	    SS2, unselected_resources, S);
	}
	if( SS.trywithout(r, &SS2) )
	{
	  ShiftDoExpand(shift, shift_index, resources, resources_index + 1,
	    SS2, unselected_resources + r, S);
	}
      }
    }

  And SS.trywith(r, &SS2) uses r->open_index to index an array within
  SS and return SS2.  Some types:

    KHE_DRS_SHIFT_ASST - a set of assignments to one shift.  Not
      quite sure yet how to represent this.
    KHE_DRS_SHIFT_ASST_TRIE - a trie holding shift assignment
      lists, indexed by a set of resources.

14 September 2022.  Added a resource set type to the solver.  This
  will be clearer than trying to re-use a single open_resources array.
  Closed the recursive loop with a clean compile.  It's all audited
  and it seems to be right.  I've also made sure that drs->expand_cost
  is set and used correctly.

  Places in the old code where drs->expand_cost is set and used:

  (1) KheDrsSolnExpand initializes drs->expand_cost to prev_soln->cost.
      This is part of shift assignment too so nothing more needed here.

  (2) KheDrsResourceSetAssignments moves any unavoidable cost into
      drs->expand_cost.  This will work for shifts as well, provided they
      use dr->expand_assignments as the source of their own assignments.

  (3) KheDrsAssignmentTry saves and resets drs->expand_cost.  In between,
      it adds dt->asst_cost (which must be the cost of dt not included in
      expression trees) and asst->cost, which is the resource constraint
      cost, not including unavoidable cost.  I've now moved this code
      into a new function, KheDrsAssignmentAddCostAndMustAssign, which
      can be re-used for shifts.

  (4) KheDrsAssignmentTry uses drs->expand_cost for pruning the search.
      Shifts should do this too every time it changes.

  (5) KheDrsMakeEvaluateAndMeldSoln uses drs->expand_cost as the
      full cost of the new solution.  This is part of shift assignment
      too so nothing more needed here.

16 September 2022.  I thought about replacing the trie by a binary tree.
  It's a more direct expression of the all-subsets idea, but it would
  be slower at run time since in each shift there would be one level
  for each open resource, not one level for each available resource
  like we have with the trie.

  Added code for including free day assignments.  Bit messy but it
  should work.  The alternative would be to build a special shift
  for being free, and place it at the end of the list of shifts,
  or indeed anywhere.  But we would still have to ensure that
  every resource was assigned to some shift.
  
  Added a minimalist KHE_DRS_EXPANDER type.

17 September 2022.  Fleshed out the KHE_DRS_EXPANDER type with
  another field (avail_resource_count) and several operations.
  Tidied up the code, it is in pretty good shape now, much
  better than before I embarked on expansion by shifts.

  Wrote KheDrsShiftAsstTrieBuild, although not adding the
  assignments.

18 September 2022.  Revised the theory section as a way of
  getting into generating the assignments.

19 September 2022.  Thinking about handling shift signatures.

  Removed planned_task_class and planned_task_on_day from
  KHE_DRS_ASSIGNMENT.  Instead, we use task_on_day to store
  one example of a suitable task, and later we go back to
  its task class to get the equivalent of planned_task_class.

20 September 2022.  Thinking about shifts.

21 September 2022.  Still thinking about shifts.  Implemented
  KheDrsExprShiftIndex, which will probably be a help.  Wrote
  various functions:

    KheDrsExprForEachShiftChild - we need this but iterating over
    children with a given (open_day_index, shift_index) - done.

    KheDrsExprOpenChildrenAtOrAfter - we need this but rating
    the previous days and the shift of interest as "before".
    Done it, in KheDrsExprOpenChildrenAtOrAfterExceptShift.

    KheDrsExprOpenDayIsLast - we need a version of this that
    is true when we are on the last open day and there is
    only one shift.  KheDrsExprOpenDayIsLastWithSingleShift
    is done now and implements this.

  and then KheDrsExprIntSumCostEvalShiftSignature, which is like
  KheDrsExprIntSumCostEvalSignature except that it does the job
  when extending a d_k-complete solution by one shift.  Then wrote
  KheDrsExprEvalShiftSignature.

  Setting nr_internal_today in each shift, but there is no
  signature yet in shifts and no code for setting it.

22 September 2022.  Audited KheDrsExprEvalShiftSignature, fixed
  several small things, all seems good now.  Removed task_classes
  field from days, they are being got at now via their shifts.

  Hurrah, now building shift open expressions and dom tests, except
  that KheDrsShiftDomTest is still to do.

24 September 2022.  Implemented KheDrsShiftDomTest, which basically
  just calls KheDrsExprIntSumCostShiftDomTest, also now implemented.
  Also did a careful audit of both KheDrsExprIntSumCostDomTest and
  KheDrsExprIntSumCostShiftDomTest, which were causing me problems,
  but now I think they are both sorted out, with the right way of
  thinking about them well expressed in comments above the functions.

25 September 2022.  Worked on the documentation.

26 September 2022.  Worked on the documentation of shift-based
  selection, which is in pretty good shape now.

27 September 2022.  Now saving one assignment for each open resource
  in each shift, and clearing these assignments out at the end of
  each expansion, along with the trie.

  Updated KHE_DRS_SHIFT_ASST to contain the new, correct fields.
  Also replaced drs->expand_prev_assts correspondingly.  Have
  clean compile.

28 September 2022.  Ideas for renaming types that inherit sigs:

    Current                Suggestion (rather radical)
    ---------------------------------------------------------
    KHE_DRS_SIGNATURE      KHE_DRS_SOLN (abstract supertype)
    KHE_DRS_SOLN           KHE_DRS_DAY_SOLN
    KHE_DRS_SHIFT_ASST     KHE_DRS_SHIFT_SOLN
    KHE_DRS_ASSIGNMENT     KHE_DRS_RESOURCE_SOLN
    ---------------------------------------------------------

  But it would mean that everyone would be adding their cost
  and signature to a solution, which sounds pretty good.

29 September 2022.  Thinking of removing inherit signature from
  resource assignment objects.  The signature can be got from
  the task on day's shift.  But what if there is no task on day?
  Where do we get the free day signature from?

  I tried removing signature fields from KHE_DRS_ASSIGNMENT, on
  the grounds that it can get its signature from its resource.  It
  might work, but replacing expand_assignments in KHE_DRS_RESOURCE
  by expand_signatures does not work, because there is one
  assignment per task class, not one per shift.

30 September 2022.  A more cautious approach might be best first.
  Add signatures to resources, one per shift and a free day one,
  and then remove signatures from assignments.  So then assignments
  would not be solutions.  In fact there would not be a hierarchy of
  solutions at all, because signatures would not be solutions.
  The rationale for this is that the signature is the same
  throughout the shift.

  Removed signatures from KHE_DRS_ASSIGNMENT.  This has made the
  28 September ideas redundant.

  Started work on KheShiftAsstTreeBuildAssts.

1 October 2022.  Working on KheShiftAsstTreeBuildAssts.  I still
  don't know what I'm doing, but I'm doing it anyway.

  Looked at this but decided to do nothing about it now, because
  every case is slightly different and a single function to add
  both the expression and its dom test would not work well:

    INHERIT_KHE_DRS_DOM_TEST_SET could include internal_today.
    The point is that if there is a set of dom tests there is
    also a set of expressions whose evaluation produces values
    that dominance testing compares using these dom tests.
    Also, there is no real need to inherit these two fields
    into the object, they could just be attributes of it.

  Inheriting KHE_DRS_DOM_TEST_SET allows it to be passed by
  reference with minimum trouble.

2 October 2022.  Added code for melding a new shift asst object
  into a set of shift assignment object.  This is just the usual
  meld operation, only with different types.  It would be good
  to generalize this, later on, but not easy because of the
  free operation, which requires a concrete type.

3 October 2022.  Did some auditing.

4 October 2022.  Audited all shift code, and completed
  KheShiftAsstTreeBuildAssts.  Done quite a lot of tidying up,
  including stuff that reduces the amount of code duplication
  between by resources and by shifts, which is good.

5 October 2022.  Changed KHE_DRS_ASSIGNMENT to KHE_DRS_RESOURCE_ASST
  to downplay its importance.  Need to audit its functions.

  I've sorted out what to do with KHE_DRS_SIGNATURE and implemented
  it.  I've retained it as a pointer type, but replaced all the
  inherits by fields, except in KHE_DRS_SOLN where the signature
  is inlined (type "struct khe_drs_signature_rec") but purely to
  save space.  It is passed from there by reference.  Also changed
  the name of the signature's "sig" field, it is now called "states".

6 October 2022.  Done some more tidying up.  All good.

9 October 2022.  Two days of chemotherapy, but back at work today
  and feeling back to normal.  Revised KHE_DRS_RESOURCE_ASST, it's
  in very good shape now.

10 October 2022.  I've checked the current file arrangement against the
    documentation.  I moved signatures in the doc from solutions to
    dominance, and moved RESOURCE_ASST in both the doc and the code
    from solutions to resources (like SHIFT_ASST is in shifts).  Also
    added new sections for shifts.  But the out of date contents of
    these and other sections are still out of date.

    Added KHE_DRS_DOMINATOR.  In the middle of the fallout.

12 October 2022.  Finished working through the KHE_DRS_DOMINATOR fallout.
  Started in on a general audit.  Deleted KheDrsTaskOrganizeDays, no
  need for it.  Then started in on a general audit.

13 October 2022.  Working on the general audit.

  Could KheDrsDominatorEvalSignature create the signature
  as well as populate it?  No, because in soln objects a
  creation would be wrong; but it could clear it, although
  even there there are cases where it must already be clear.
  And actually we initialize the cost field of the solution
  signature separately, so clearing out would be flat wrong.

  KheDrsResourceOnDayLeafSet, KheDrsDominatorEvalSignature,
  and KheDrsResourceOnDayLeafClear are more or less all
  called together.  Review these calls and see if there
  is any common structure we can exploit for simplifying.
  Not really, because we can make multiple calls to
  KheDrsResourceOnDayLeafSet and KheDrsResourceOnDayLeafClear
  per call to KheDrsDominatorEvalSignature, or just one.

  KheDrsResourceSignatureBuild and KheDrsShiftMakeEvaluateAndMeldSoln
  share some structure but not other structure.  So perhaps a
  refactoring is needed so that corresponding functions have
  the same structure.  No, KheDrsResourceSignatureBuild really
  does just build a signature and nothing more, it is different.

  Sorted out KheDrsDominatorClear.  Dominators are clear
  initially and while closed, so they do not need to be
  and are not cleared during opening.  They are cleared
  during closing.  Every resource on day dom test is
  also a day dom test, so although we clear the resource
  on day dom tests array we do not free the individual
  dom tests in that array; they are freed when clearing
  the day dominator.

14 October 2022.  Finished the general audit, but not the
  new shift stuff.  Removed the day field from solutions,
  which will save a significant amount of space, with no
  significant running time cost except as follows.

  KheDrsSolvePrioritySearch contains one call to KheDrsSolnDay
  which is going to be slower than we want.  I've decided that
  as a cost per solution it's not too bad.  Usually KheDrsSolnDay
  returns quickly.  Anyway I can't see what else to do there.

  Started auditing the documentation.  Up to the start of "B.4 Events".

15 October 2022.  Auditing the documentation.  Going well,
  currently in the middle of shift assignment tries.

16 October 2022.  Still revising the documentation.  Just
  finished expression opening.

17 October 2022.  Still revising the documentation.  Up to
  expansion, which seems to be a can of worms.

    Submodule       Functions                        Lines
    ------------------------------------------------------------
    RESOURCE        KheDrsResourceExpandBegin    2678-3122   444
    RESOURCE_ASST   KheDrsResourceAsstAdd        4010-4236   226
    SHIFT_ASST	    KheDrsShiftAsstTrieExpand    6783-7058   275
    SHIFT           KheDrsShiftExpandByShifts    6362-6439    77 
    EXPANDER        KheDrsExpanderMake          17721-17834  113
    expansion       KheDrsSolnExpandAdd         17852-18197  345
    ------------------------------------------------------------

  Reorganized it:  promoted it to a major category, so that we
  we can have subsections in the doc.  Made those subsections
  (just stubs so far), and also made new submodules in the code
  for the expansion aspects of the various earlier types.

  Who calls who in expansion?

      KheDrsSolnExpand
        KheDrsExpanderMake
	KheDrsExpanderAddMustAssign
	KheDrsResourceExpandBegin
	KheDrsShiftExpandBegin (in fact there are two parts to this)
	  KheDrsShiftAsstTrieBuild
	    KheShiftAsstTreeBuildAssts
	      KheDrsShiftMakeEvaluateAndMeldSoln
	        KheDrsShiftAsstTrieMeldShiftAsst
	      KheShiftAsstTreeBuildAssts (recursion)
	    KheDrsShiftAsstTrieBuild (recursion)
	KheDrsSolnExpandByShifts
	  KheDrsSolnExpandMakeAndMeld
	  KheDrsShiftExpandByShifts
	  KheDrsShiftAsstTrieExpandByShifts
	    KheDrsShiftAsstExpandByShifts
	      KheDrsSolnExpandByShifts (recursion)
	    KheDrsShiftAsstTrieExpandByShifts (recursion)
	KheDrsSolnExpandByResources
	  KheDrsSolnExpandMakeAndMeld
	    KheDrsSolnSetMeldSoln
	  KheDrsResourceAsstExpandByResources
	    KheDrsResourceAsstDoExpandByResources
	      KheDrsExpanderAddTaskOnDayAndCost
	      KheDrsResourceAsstAdd
	      KheDrsSolnExpandByResources (recursion)
	      KheDrsResourceAsstDelete
	KheDrsShiftExpandEnd
	KheDrsResourceExpandEnd

    The best thing, probably, is to present the algorithm
    top-down, without worrying very much about which submodule
    the code lies in.  We can divide it up like this:

	-----------------------------------------------------------------
        Expanders (all operations)
	-----------------------------------------------------------------
        KheDrsSolnExpand (it's high level, we can leave stuff to later)
	and drs->expand*
	KheDrsSolnExpandAdd
	KheDrsSolnExpandDelete
	KheDrsSolnExpandMakeAndMeld
	  KheDrsSolnSetMeldSoln
	luckily this correspondings with submodule "Solution expansion"
	except that that submodule also has KheDrsSolnExpandByResources
	and KheDrsSolnExpandByShifts.
	-----------------------------------------------------------------
	KheDrsResourceExpandBegin  and dr->expand_*
        KheDrsResourceExpandEnd
	-----------------------------------------------------------------
	KheDrsSolnExpandByResources
	  KheDrsSolnExpandMakeAndMeld
	    KheDrsSolnSetMeldSoln
	  KheDrsResourceAsstExpandByResources
	    KheDrsResourceAsstDoExpandByResources
	      KheDrsExpanderAddTaskOnDayAndCost
	      KheDrsResourceAsstAdd
	      KheDrsSolnExpandByResources (recursion)
	      KheDrsResourceAsstDelete
	-----------------------------------------------------------------
	KheDrsShiftExpandBegin (in fact there are two parts to this)
	  KheDrsShiftAsstTrieBuild
	    KheShiftAsstTreeBuildAssts
	      KheDrsShiftMakeEvaluateAndMeldSoln
	        KheDrsShiftAsstTrieMeldShiftAsst
	      KheShiftAsstTreeBuildAssts (recursion)
	    KheDrsShiftAsstTrieBuild (recursion)
	-----------------------------------------------------------------
	KheDrsSolnExpandByShifts
	  KheDrsSolnExpandMakeAndMeld
	  KheDrsShiftExpandByShifts
	  KheDrsShiftAsstTrieExpandByShifts
	    KheDrsShiftAsstExpandByShifts
	      KheDrsSolnExpandByShifts (recursion)
	    KheDrsShiftAsstTrieExpandByShifts (recursion)
	-----------------------------------------------------------------

   Here's a concrete proposal:

	Expanders
	The main solution expansion function
	Setting up resources for expansion
	Expansion by resources
	Setting up shifts for expansion
	Setting up shift assignment tries for expansion
	Expansion by shifts

  Also removed expand_prev_signatures.

19 October 2022.  Still revising the expansion documentation.
  Lost most of yesterday to cancer stuff.

20 October 2022.  Still revising the expansion documentation.
  Audited what I've done up to this point, it's all good.

21 October 2022.  Still revising the expansion documentation.
  Reached the end, and I've verified that every expansion
  function is documented, but now it all needs to be pondered
  and carefully audited.

22 October 2022.  Finished revising the expansion documentation.

23 October 2022.  Brought cross references up to date in the
  full User's Guide.  Off-site backup today.

26 October 2022.  Took a couple of days off to do some refereeing
  and clear my head.  Back on to shift assignment today.  I've
  read carefully through the documentation and it's pretty good.

  When building shift assignments, we need to check that the
  resources R are all either not fixed or fixed to tasks within
  the current shift, otherwise we won't be able to assign them
  when the time comes.  I've just done this.

  Brought the Testing documentation up to date.  It's good now.

27 October 2022.  KheDrsSolnResourceIsAssigned, which is called
  by KheDrsResourceOnDayIsFixed, does indeed assume that there
  is a task on day object for every day from first to last.
  So we need to resurrect KheDrsTaskOrganizeDays to make sure
  there is a task on day object for every day of the task, and
  then check all uses of dtd->task or whatever to make sure
  that a NULL value is handled correctly.  Perhaps change
  the field name temporarily to make sure we find them all.

  Here is what the doc says, and we have to make it good:

    "A multi-day task is considered by the solver to be running on all
    days from its first busy day to its last (inclusive).  If there is
    an intermediate day when the task is not running, there is still
    a task on day object for that day, but its @C { task } and @C { time }
    fields are @C { NULL }."

    "The @C { closed_asst } field holds the resource on day object
     that this task on day is assigned to when the task is closed.
     It is non-@C { NULL } exactly when the adjacent @C { task }
     field and the @C { closed_asst } field of the enclosing DRS
     task are both non-@C { NULL }."

  When there is a gap in a grouped task and some other task fills
  the gap, the solver is not going to be able to find that solution,
  so the tasks involved have to remain closed.

  Stop press!  Better to reject tasks with gaps.
  Implemented KheTaskClassNoGaps.

28 October 2022.  I reworked the spec today into something that
  seems practical.  I've done all the hard requirements, soft
  requirements are next.

29 October 2022.  Did a careful audit of the cases when no solver
  is made; there is now a clear and documented correspondence
  between the dot points in the documentation and the cases
  in the code where NULL is returned.  I've also checked that
  the numbered points about which tasks are selected are correct.

  Finally I got to look again at the KheDrsTaskOrganizeDays mess.
  I've concluded that now there are no gaps, we do need to sort
  the task on day objects chronologically but nothing else is
  needed.  Every task on day object has a non-NULL task field
  and a non-NULL time field.  I have looked carefully at
  KheDrsResourceOnDayIsFixed, and it's fine when every
  resource is assigned at most once per day and there are
  no gaps in the tasks.

  I thought of shift-based selection before I left for PATAT, and
  started work on it on 11 September.  That was over six weeks ago, 
  but I've done a lot of reorganizing and documenting as well.

30 October 2022.  Found a use for ds->expand_must_assign_count.
  Perhaps not everything that it could be used for, but something.

  Long ago I asked myself "What if KheDrsResourceOnDayIsFixed returns
  a task that some other resource has already been assigned to?  Is
  that possible?"  I've now looked through KheDrsResourceOnDayIsFixed
  and convinced myself that it is not possible.

1 November 2022.  Testing today, at last.  Wrote some debug code
  for printing a few shift asst tries.  I've fixed one or two
  tiny bugs so far, not helped by a totally mad "where" from gdb.
  Have clean run, but is it correct?

3 November 2022.  Testing.  Added use_expand_by_shifts option
  and documented it.  Expansion by shifts is the default.
  Improved the debug output of shift asst tries, and fixed
  a bug with building asst tries.  They look pretty good,
  but are they correct?  Actually this one looks correct:

    [ ShiftAsstTrie
      [ {}: {}
        [ {HN_1}: {(<0.00000> 1Mon:Early.5)}
          [ {HN_1, NU_6}: {(<0.00000> 1Mon:Early.1, 1Mon:Early.5)}
            [ {HN_1, NU_6, NU_9}: {(<0.00000> 1Mon:Early.1, 1Mon:Early.5, 1Mon:Early.6)}
            ]
          ]
          [ {HN_1, NU_9}: {(<0.00000> 1Mon:Early.1, 1Mon:Early.5)}
          ]
        ]
        [ {NU_6}: {(<0.00000> 1Mon:Early.5)}
          [ {NU_6, NU_9}: {(<0.00000> 1Mon:Early.5, 1Mon:Early.6)}
          ]
        ]
        [ {NU_9}: {(<0.00000> 1Mon:Early.5)}
        ]
      ]
    ]

  because 1Mon:Early.5 is a "must-assign".  This explains why
  the list of solutions for the empty set of resources is empty,
  and why {HN_1} is assigned to it.

6 November 2022.  Lost a few days to chemo.  I've been doing some
  testing and fiddling with the debug output this morning.  It looks
  as though the shift assignment tries are correct, and there is
  plenty of expansion going on.
  
  I've also got a good handle on how shift assignments are being
  combined into full assignments, thanks to some good debug output.
  It all looks good.
  
  This is the final result bar from 3 resources assign by shifts:

    [ "INRC2-4-030-1-6291", 1 solution, in 13.3 secs: cost 0.02000 ]

  And this is it for 3 resources assign by resources:

    [ "INRC2-4-030-1-6291", 1 solution, in 17.4 secs: cost 0.02000 ]

  So we are already seeing some speedup.  Now what about four resources?
 
  Tried four resources.  For ordinary nurses it's faster but not
  dramatically so, but for trainees (scraped from the graph), by
  shifts is 1.2906 secs, by resources is 1283.5438 secs.

  But getting a very slow run on TR_25, TR_26, TR_28, TR_29 with
  days 0-13.  Not sure why these resources have done this, but
  it is seriously slow.  However it found an improvement:

      rdv 88.0 mins (new best, 0.01970 < 0.02000)

  So that's an encouraging sign.

7 November 2022.  Discovered this output at the end of yesterday's slow run:

    [ KheDrsRerun inconsistent monitor
      ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1 with cost 0.00030:
      open_and_search_cost 0.00060
    ]
    [ KheDrsRerun inconsistent monitor
      ARC:NA=s30|NWTrainee=h1:1/1Tue:Late/NA=s30|NWTrainee=h1:1 with cost 0.00030:
      open_and_search_cost 0.00060
    ]
    [ KheDrsRerun inconsistent monitor
      ARC:NA=s30|NWTrainee=h1:1/1Thu:Night/NA=s30|NWTrainee=h1:1 with cost 0.00030:
      open_and_search_cost 0.00060
    ]
    [ KheDrsRerun inconsistent monitor
      ARC:NA=s30|NWTrainee=h1:1/1Sat:Day/NA=s30|NWTrainee=h1:1 with cost 0.00000:
      open_and_search_cost 0.00030
    ]
    [ KheDrsRerun inconsistent monitor
      ARC:NA=s30|NWTrainee=h1:1/2Mon:Early/NA=s30|NWTrainee=h1:1 with cost 0.00000:
      open_and_search_cost 0.00030
    ]
    [ KheDrsRerun inconsistent monitor
      ARC:NA=s30|NWTrainee=h1:1/2Tue:Early/NA=s30|NWTrainee=h1:1 with cost 0.00000:
      open_and_search_cost 0.00030
    ]
    [ KheDrsRerun inconsistent monitor
      ARC:NA=s30|NWTrainee=h1:1/2Thu:Late/NA=s30|NWTrainee=h1:1 with cost 0.00000:
      open_and_search_cost 0.00030
    ]
    [ KheDrsRerun inconsistent monitor
      ARC:NA=s30|NWTrainee=h1:1/2Sun:Early/NA=s30|NWTrainee=h1:1 with cost 0.00030:
      open_and_search_cost 0.00060
    ]

  They are all being rated as costing 30 more than they should.  So
  when is this extra cost being added in?  Presumably the initial
  cost is correct but then something gets added incorrectly?
  Something to do with ARC costs being shunted off to the expander, or to
  non_asst_cost?  Should this monitor be monitored anyway?

8 November 2022.  Here's the run with the new "init" stuff as well:

	   os init: 0.00000 + 0.00030                                (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
	   oc init: 0.00000 + 0.00030                                (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
	  os open : 0.00030 + 0.00000 - 0.00030                      (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
	  oc open : 0.00030 + 0.00000 - 0.00030                      (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
    [ KheDrsSolveSearch(4 resources, 14 days)
      KheDrsSolveSearch expanding into 1Mon
      KheDrsSolveSearch expanding into 1Tue
	  os searc: 0.00000 + 0.00030 - 0.00000                      (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
	  os searc: 0.00030 + 0.00030 - 0.00000                      (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
      KheDrsSolveSearch expanding into 1Wed
      ...
      KheDrsSolveSearch expanding into 2Sun
    ] KheDrsSolveSearch returning true
	  oc close: 0.00000 + 0.00030 - 0.00000                      (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1
	monitor cost         0.00030
	open_and_search_cost 0.00060
      ]
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/1Tue:Late/NA=s30|NWTrainee=h1:1
	monitor cost         0.00030
	open_and_search_cost 0.00060
      ]
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/1Thu:Night/NA=s30|NWTrainee=h1:1
	monitor cost         0.00030
	open_and_search_cost 0.00060
      ]
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/1Sat:Day/NA=s30|NWTrainee=h1:1
	monitor cost         0.00000
	open_and_search_cost 0.00030
      ]
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/2Mon:Early/NA=s30|NWTrainee=h1:1
	monitor cost         0.00000
	open_and_search_cost 0.00030
      ]
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/2Tue:Early/NA=s30|NWTrainee=h1:1
	monitor cost         0.00000
	open_and_search_cost 0.00030
      ]
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/2Thu:Late/NA=s30|NWTrainee=h1:1
	monitor cost         0.00000
	open_and_search_cost 0.00030
      ]
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/2Sun:Early/NA=s30|NWTrainee=h1:1
	monitor cost         0.00030
	open_and_search_cost 0.00060
      ]

  And here is a cut-down version, showing just the open and search costs for
  monitor ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1:

	   os init: 0.00000 + 0.00030
	  os open : 0.00030 + 0.00000 - 0.00030
      KheDrsSolveSearch expanding into 1Tue
	  os searc: 0.00000 + 0.00030 - 0.00000                      (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
	  os searc: 0.00030 + 0.00030 - 0.00000                      (ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1)
      [ KheDrsRerun inconsistent monitor
	ARC:NA=s30|NWTrainee=h1:1/1Tue:Day/NA=s30|NWTrainee=h1:1
	monitor cost         0.00030
	open_and_search_cost 0.00060
      ]

  Recall that the first cost on any line is the previous current cost,
  and after that it shows the changes.  There seem to be two updates
  to the cost on 1Tue, when I would have expected only one.

  The problem was that event resource monitor expressions were being
  evaluated twice during a rerun when doing it by shifts:  once when
  constructing the shift assignments, and then again when constructing
  the chosen solution.  I've now turned off debugging when constructing
  shift assignments, by way of a boolean flag in the dominator, and
  it has fixed the problem.

  Here is the run showing why it is taking so long:

    [ KheDynamicResourceSolverDoSolve(drs, false, use_extra_selection, expand_by_shifts,
	0, 0, 0, IndexedUniform, false, None) cost 0.02000
      resources:  TR_25, TR_26, TR_28, TR_29
      day ranges: 0-13
      [ KheDrsSolveSearch(4 resources, 14 days)
	KheDrsSolveSearch begin expand into 1Mon (1 solns)
	KheDrsSolveSearch begin expand into 1Tue (621 solns)
	KheDrsSolveSearch begin expand into 1Wed (2200 solns)
	KheDrsSolveSearch begin expand into 1Thu (3362 solns)
	KheDrsSolveSearch begin expand into 1Fri (7152 solns)
	KheDrsSolveSearch begin expand into 1Sat (43467 solns)
	KheDrsSolveSearch begin expand into 1Sun (38506 solns)
	KheDrsSolveSearch begin expand into 2Mon (22491 solns)
	KheDrsSolveSearch begin expand into 2Tue (6881 solns)
	KheDrsSolveSearch begin expand into 2Wed (23997 solns)
	KheDrsSolveSearch begin expand into 2Thu (78216 solns)
	KheDrsSolveSearch begin expand into 2Fri (54088 solns)
	KheDrsSolveSearch begin expand into 2Sat (19089 solns)
	KheDrsSolveSearch begin expand into 2Sun (5108 solns)
      ] KheDrsSolveSearch returning true
    ]

  The cost improves from 2000 to 1970.  But it takes forever.
  A run by resources produces the same number of solutions
  on each day, which makes sense when you think about it.
  It's a good correctness check.  Altogether I think we can
  declare expansion by shifts to be done and dusted.

9 November 2022.  Implemented min and max resources per shift,
  including Implemented KheDrsDayExpandBegin and KheDrsDayExpandEnd.
  Seems to be working.  It's running now and producing the same
  number of solutions per day as above, although I can't say
  that there is any noticeable speedup.

  The old run took 100 minutes, according to my paper.  I seem to
  have reduced this down to only about 80 minutes, which is not
  wonderful.  Actually 90.2 minutes for the full run, but the
  last one takes up nearly all of that time.

  Here is a first and last dominates comparison:

    [ KheDrsSolnDominatesDebug(soln1 0.01775, soln2 0.01865)
	Res    Avail  Details
      ----------------------------------------------------------------
       true  0.00090  sig cost
       true  0.00090  sig[ 0] 11 11  cbtc 5-11|s20     Constraint:11/HN_1
       true  0.00090  sig[ 1]  0  0  laic 2-5|s15      Constraint:14/HN_1
       true  0.00090  sig[ 2]  0  0  laic 2-5|s15      Constraint:16/HN_1
       true  0.00090  sig[ 3]  0  0  laic 3-5|s15      Constraint:17/HN_1
       true  0.00090  sig[ 4]  0  0  laic 2-5|s30      Constraint:19/HN_1
    ** true  0.00060  sig[ 5]  1  3  laic 2-4|s30      Constraint:22/HN_1
       true  0.00060  sig[ 6]  1  1  laic 2-28|s15     Constraint:15/HN_1
       true  0.00060  sig[ 7]  0  0  cbtc 0-1|h1       Constraint:2/HN_1/1Fri3
       true  0.00060  sig[ 8]  0  0  cbtc 0-1|h1       Constraint:3/HN_1/1Fri4
       true  0.00060  sig[ 9]  1  1  lbtc 0-1|h1       Constraint:1/HN_1/16
       true  0.00060  sig[10] 11 11  cbtc 15-22|s20    Constraint:13/NU_6
       true  0.00030  sig[11]  3  1  laic 2-5|s15      Constraint:14/NU_6
       true  0.00030  sig[12]  0  0  laic 2-5|s15      Constraint:16/NU_6
       true  0.00030  sig[13]  0  0  laic 3-5|s15      Constraint:17/NU_6
      false -0.00030  sig[14]  3  1  laic 3-5|s30      Constraint:23/NU_6
      ----------------------------------------------------------------
    ]

  The one I've marked ** looks queer.  The two signature values on the
  previous line are equal, yet there is a drop of 30 in available cost.
  In fact, all is well.  The drop in cost relates to the current line,
  not the previous line, and 30 is correct (it applies when there is
  no continuation of the current active interval).

  Constraint:14 is 2-5 consecutive Early shifts; Constraint:23 is 3-5
  consecutive working days.  These are correlated, but not in a good way.
  If NU_6 follows up with four early shifts, soln1 pays 30 + 60, soln2
  pays 0.  So there really is no dominance here.  And the best way to
  separate these cases seems to be to generate the next solutions.

  How does by shifts and by resources compare on the number of
  solutions made, as opposed to the number kept?  By shifts:

    [ KheDrsSolveSearch(3 resources, 14 days)
      KheDrsSolveSearch expanded 1Mon (made 61, undominated 61)
      KheDrsSolveSearch expanded 1Tue (made 1585, undominated 308)
      KheDrsSolveSearch expanded 1Wed (made 2689, undominated 391)
      KheDrsSolveSearch expanded 1Thu (made 2608, undominated 408)
      KheDrsSolveSearch expanded 1Fri (made 108, undominated 18)
      KheDrsSolveSearch expanded 1Sat (made 54, undominated 12)
      KheDrsSolveSearch expanded 1Sun (made 64, undominated 37)
      KheDrsSolveSearch expanded 2Mon (made 882, undominated 108)
      KheDrsSolveSearch expanded 2Tue (made 3894, undominated 813)
      KheDrsSolveSearch expanded 2Wed (made 83, undominated 12)
      KheDrsSolveSearch expanded 2Thu (made 97, undominated 12)
      KheDrsSolveSearch expanded 2Fri (made 17, undominated 10)
      KheDrsSolveSearch expanded 2Sat (made 53, undominated 7)
      KheDrsSolveSearch expanded 2Sun (made 0, undominated 0)
    ] KheDrsSolveSearch returning false

  By resources:

    [ KheDrsSolveSearch(3 resources, 14 days)
      KheDrsSolveSearch expanded 1Mon (made 61, undominated 61)
      KheDrsSolveSearch expanded 1Tue (made 2581, undominated 308)
      KheDrsSolveSearch expanded 1Wed (made 2545, undominated 391)
      KheDrsSolveSearch expanded 1Thu (made 2608, undominated 408)
      KheDrsSolveSearch expanded 1Fri (made 108, undominated 18)
      KheDrsSolveSearch expanded 1Sat (made 54, undominated 12)
      KheDrsSolveSearch expanded 1Sun (made 68, undominated 37)
      KheDrsSolveSearch expanded 2Mon (made 789, undominated 108)
      KheDrsSolveSearch expanded 2Tue (made 3151, undominated 813)
      KheDrsSolveSearch expanded 2Wed (made 83, undominated 12)
      KheDrsSolveSearch expanded 2Thu (made 81, undominated 12)
      KheDrsSolveSearch expanded 2Fri (made 14, undominated 10)
      KheDrsSolveSearch expanded 2Sat (made 53, undominated 7)
      KheDrsSolveSearch expanded 2Sun (made 0, undominated 0)
    ] KheDrsSolveSearch returning false

  Amazingly, sometimes by resources makes fewer solutions.  What
  is going on?  Is it one-extra and two-extra selection?  Turn them off:

    [ KheDrsSolveSearch(3 resources, 14 days)
      KheDrsSolveSearch expanded 1Mon (made 285, undominated 61)
      KheDrsSolveSearch expanded 1Tue (made 6785, undominated 308)
      KheDrsSolveSearch expanded 1Wed (made 10044, undominated 391)
      KheDrsSolveSearch expanded 1Thu (made 5587, undominated 408)
      KheDrsSolveSearch expanded 1Fri (made 108, undominated 18)
      KheDrsSolveSearch expanded 1Sat (made 180, undominated 12)
      KheDrsSolveSearch expanded 1Sun (made 248, undominated 37)
      KheDrsSolveSearch expanded 2Mon (made 3817, undominated 108)
      KheDrsSolveSearch expanded 2Tue (made 26889, undominated 813)
      KheDrsSolveSearch expanded 2Wed (made 83, undominated 12)
      KheDrsSolveSearch expanded 2Thu (made 236, undominated 12)
      KheDrsSolveSearch expanded 2Fri (made 35, undominated 10)
      KheDrsSolveSearch expanded 2Sat (made 121, undominated 7)
      KheDrsSolveSearch expanded 2Sun (made 0, undominated 0)
    ] KheDrsSolveSearch returning false

  Yep, amazingly enough one-extra and two-extra selection are often
  competitive (in number of solutions made) with by shifts.

10 November 2022.  First thoughts about combining one-extra and
  two-extra selection with selection by shifts.  The two ideas
  do seem to be independent.  For example, two-extra might find
  that (r1 := Night, r2 := Day) dominates (r1 := Day, r2 := Night),
  which is quite unlike anything done by expansion by shifts.  So
  it makes sense to include both ideas in one algorithm, if we
  can only implement it.  The problem is that by shifts does not
  use the assignment data type, where the skip counts and skip
  lists are.  And if it did use the assignment data type, it
  would be for a specific task within the assignment's task
  class, whereas assignments are for the task class generally.

  Replace task on day with (assignment, task on day) so that we
  get both.  We may be able to implement the whole thing as an
  add-on.

  Added a fixed resource assignment type and replaced a couple
  of fields with arrays of that type.  Also made sure that each
  assignment is on the other's skip list, since either could be
  decided on before the other.  Clean compile but needs an audit
  and skip stuff not implemented yet.

11 November 2022.  Replaced KheDrsResourceAsstAdd and similar
  functions by KheDrsFixedResourceAsstAdd and similar functions.
  Fixed assignments have actually improved things slightly.

  If two assignments are on each other's skip lists, then a shift
  assignment with those two tasks (quite likely in practice) is no
  use.  Can check this when building the shift assignment.  Wrote
  KheDrsFixedResourceAsstIsCompatible to check this.

12 November 2022.  Auditing revised expansion by shifts.  I've
  added (actually restored) dr->expand_free_day_asst, and used
  it in KheDrsSolnExpandByShifts, the free day part, which is
  now re-implemented using free day assignment objects, so that
  skip counts are included in what it does.

  Currently working on a completely new KheDrsShiftAsstTrieBuildAssts.
  All written but needs a careful audit.

13 November 2022.  Auditing the new KheDrsShiftAsstTrieBuildAssts.

  KheDrsResourceExpandBegin has its own unique function.  It builds
  signatures and assignments used by them.  The trie traversing part
  of KheDrsShiftExpandInitAsstTrie (KheDrsShiftAsstTrieBuild) also
  has its own unique function.  KheDrsShiftAsstTrieBuildAssts is
  relevant though:

  KheDrsShiftAsstTrieBuildAssts (3)
    KheDrsShiftMakeEvaluateAndMeldSoln (1)
    KheDrsResourceAsstShiftAsstTrieBuildAssts (2)
      KheDrsResourceAsstDoAsstShiftAsstTrieBuildAssts (2a)
	KheDrsShiftAsstTrieBuildAssts (3)

  KheDrsSolnExpandByShifts (4)
    KheDrsSolnExpandMakeAndMeld (1)
    KheDrsShiftExpandByShifts (5)
      KheDrsShiftAsstTrieExpandByShifts (5)
        KheDrsShiftAsstExpandByShifts (6)
	  KheDrsSolnExpandByShifts (4)

  KheDrsSolnExpandByResources (3)
    KheDrsSolnExpandMakeAndMeld (1)
    KheDrsResourceAsstExpandByResources (2)
      KheDrsResourceAsstDoExpandByResources (2a)
	KheDrsSolnExpandByResources (3)
        
  (1) KheDrsShiftMakeEvaluateAndMeldSoln makes a different kind
  of solution from KheDrsSolnExpandMakeAndMeld:  a shift assignment
  rather than a d_k-complete solution.  This explains why the others
  append resource signatures and it doesn't, but it doesn't really
  explain why KheDrsShiftMakeEvaluateAndMeldSoln calls
  KheDrsTaskOnDayLeafSet and KheDrsTaskOnDayLeafClear when
  the others don't.  Indeed KheDrsTaskOnDayLeafSet is called
  by KheDrsFixedResourceAsstAdd and that might be a better
  way to do it, because it would handle the skip counts too.

  * We need to see whether KheDrsShiftAsstTrieBuildAssts,
  KheDrsResourceAsstShiftAsstTrieBuildAssts, and
  KheDrsResourceAsstDoAsstShiftAsstTrieBuildAssts call
  KheDrsFixedResourceAsstAdd, and if not why not.
  Yes they do, so we're doing it twice there.

  (2) and (2a).  These incorporate an assignment into the
  growing solution.  (2) takes a given assignment, converts
  it into a fixed assignment, and passes that to (2a), which
  includes it and then recurses to get more assignments.  The
  corresponding functions are identical apart from their
  parameters and the function called by (2a) to recurse.
  
  (3) These functions recurse through a set of resources, trying
  each assignment built previously by KheDrsResourceExpandBegin.
  They are identical except for the resources they recurse over
  and what they call in the end.

  (4) KheDrsSolnExpandByShifts is unique in that it recurses
  over a set of shifts rather than a set of resources.  There
  is a general resemblance to (3) replacing resources by shifts.

  (5) is about recursing over all subsets of the resources to
  try their assignments.

  (6) is about trying one shift assignment and recursing.

  Altogether it is hard to see how KheDrsSolnExpandByShifts
  could be unified with the others, but the others are very
  similar and at a minimum we should use corresponding names.

  Current names                       Alternative names (?)
  ----------------------------------------------------------------
  KheDrsShiftAsstTrieBuildAssts
  KheDrsShiftMakeEvaluateAndMeldSoln
  KheDrsResourceAsstShiftAsstTrieBuildAssts
  KheDrsResourceAsstDoAsstShiftAsstTrieBuildAssts

  KheDrsSolnExpandByResources
  KheDrsSolnExpandMakeAndMeld
  KheDrsResourceAsstExpandByResource
  KheDrsResourceAsstDoExpandByResources
  ----------------------------------------------------------------

  Before choosing better names here it would be good to choose
  new names for "d_k complete solution" and "shift assignment".
  NB the Soln functions insert their solutions into a solution
  set, the shift functions insert their solutions into a simple
  list within a trie.  But the trie itself may not be needed:

    In KheDrsShiftMakeEvaluateAndMeldSoln, dsat is used only
    to give access to the node's list of assignments.

    In KheDrsResourceAsstShiftAsstTrieBuildAssts, dsat is used only
    by passing on to KheDrsResourceAsstDoAsstShiftAsstTrieBuildAssts.

    In KheDrsResourceAsstDoAsstShiftAsstTrieBuildAssts, dsat is used
    by the recursive call to KheDrsShiftAsstTrieBuildAssts.

  * So when you add all this up, what is really being passed as
  dsat is the list of solutions that the generated solutions
  are to be melded into.  If we made that list into a separate
  object, we could pass that object and not pass dsat.  Even
  if we retain dsat these are therefore not operations on dsat;
  we could demote it to just a parameter that tags along.

  What about KHE_DRS_SHIFT_EXPANDER and KHE_DRS_RESOURCE_EXPANDER,
  both inheriting KHE_DRS_EXPANDER?  We could make the types very
  similar, in fact differing only in the types of their fields,
  not their names.  We'd be passing many fewer parameters.  And
  all these functions could have KHE_DRS_SHIFT_EXPANDER or
  KHE_DRS_RESOURCE_EXPANDER for their first parameter, which
  would keep them together in their own modules.

14 November 2022.  Removed the calls to KheDrsTaskOnDayLeafSet and
  KheDrsTaskOnDayLeafClear from KheDrsShiftMakeEvaluateAndMeldSoln.  Now
  KheDrsTaskOnDayLeafSet is called only from KheDrsFixedResourceAsstAdd,
  and KheDrsTaskOnDayLeafClear only from KheDrsFixedResourceAsstDelete.

  KheDrsSolnDominates and KheDrsShiftAsstDominates could be united,
  by replacing the ds parameter of KheDrsShiftAsstDominates with
  ds->dominator.  We could possibly replace the shift asst list
  with a solution set.  Not that it's actually needed.  But if
  soln set was an abstract supertype that included a simple list
  as one of its alternatives, there would be hardly any cost.

  KHE_DRS_SOLN_LIST contains a solver field, used only to gain access
  to a dominator.  So perhaps SOLN_SET could contain a dominator
  field and a type field and then split into the various data
  structures we need.  Even a dominance test type, although we
  may want to change that dynamically (?).

  Break KHE_DRS_DOM_KIND into two parts, the dominance structure
  and the dominance test type.  Understood that not all pairs are
  compatible.  The old type is:

    typedef enum {
      KHE_DRS_DOM_NONE,
      KHE_DRS_DOM_WEAK,
      KHE_DRS_DOM_MEDIUM,
      KHE_DRS_DOM_STRONG,
      KHE_DRS_DOM_TRIE,
      KHE_DRS_DOM_STRONG_WITH_TRADEOFF,
      KHE_DRS_DOM_TRIE_WITH_TRADEOFF,
      KHE_DRS_DOM_INDEXED_WITH_TRADEOFF,
      KHE_DRS_DOM_UNIFORM,
      KHE_DRS_DOM_INDEXED_UNIFORM
    } KHE_DRS_DOM_KIND;

  The two new types would be 

    typedef enum {
      KHE_DRS_DOM_TEST_TYPE_NONE,
      KHE_DRS_DOM_TEST_TYPE_WEAK,
      KHE_DRS_DOM_TEST_TYPE_MEDIUM,
      KHE_DRS_DOM_TEST_TYPE_STRONG,
      KHE_DRS_DOM_TEST_TYPE_TRADEOFF,
      KHE_DRS_DOM_TEST_TYPE_UNIFORM,
    } KHE_DRS_DOM_TEST_TYPE;

    typedef enum {
      KHE_DRS_SOLN_SET_TYPE_LIST,
      KHE_DRS_SOLN_SET_TYPE_HASH_WEAK,
      KHE_DRS_SOLN_SET_TYPE_HASH_MEDIUM,
      KHE_DRS_SOLN_SET_TYPE_TRIE,
      KHE_DRS_SOLN_SET_TYPE_INDEXED
    } KHE_DRS_SOLN_SET_TYPE;

  Not all solution set types support all dominance test types:

    Test type    LIST   HASH_WEAK HASH_MEDIUM TRIE INDEXED
    -----------------------------------------------------------
    NONE         Y
    WEAK                Y
    MEDIUM                        Y
    STRONG       Y                            Y     ? (should be Y)
    TRADEOFF     Y                            Y     Y
    UNIFORM      Y                                  Y
    -----------------------------------------------------------

  Here are better names:

    typedef enum {
      KHE_DRS_DOM_LIST_NONE,
      KHE_DRS_DOM_LIST_STRONG,
      KHE_DRS_DOM_LIST_TRADEOFF,
      KHE_DRS_DOM_LIST_UNIFORM,
      KHE_DRS_DOM_HASH_WEAK,
      KHE_DRS_DOM_HASH_MEDIUM,
      KHE_DRS_DOM_TRIE_STRONG,
      KHE_DRS_DOM_TRIE_TRADEOFF,
      KHE_DRS_DOM_INDEXED_STRONG,
      KHE_DRS_DOM_INDEXED_TRADEOFF,
      KHE_DRS_DOM_INDEXED_UNIFORM
    } KHE_DRS_DOM_KIND;

  Caching is a separate case at the top, so we could add CACHED to
  KHE_DRS_SOLN_SET_TYPE.  We can do all this quite separately before
  we do anything else.  We can have SOLN_SET to LIST, which just
  returns the existing list if that's what we are using.

    typedef enum {
      KHE_DRS_SOLN_DAY,			(old KHE_DRS_SOLN)
      KHE_DRS_SOLN_RESOURCE,		(old KHE_DRS_RESOURCE_ASST?)
      KHE_DRS_SOLN_SHIFT,		(old KHE_DRS_SHIFT_ASST)
    } KHE_DRS_SOLN_TYPE;

    #define INHERIT_KHE_DRS_SOLN
      struct khe_drs_signature_rec	sig;  /* inlined signature */

    typedef struct khe_drs_day_soln_rec {
      INHERIT_KHE_DRS_SOLN
      plus the usual soln fields
    } *KHE_DRS_DAY_SOLN;

    typedef struct khe_drs_resource_soln_rec {
      INHERIT_KHE_DRS_SOLN
      plus the usual resource assignment fields
    } *KHE_DRS_RESOURCE_SOLN;

    typedef struct khe_drs_shift_soln_rec {
      INHERIT_KHE_DRS_SOLN
      plus the usual shift assignment fields
    } *KHE_DRS_SHIFT_SOLN	;

  Although actually we don't do dominance testing between shift
  assignment fields - or do we?  Yes we do.  The point of all
  this is that we can have a SOLN_SET whenever we want a
  collection of solutions, and SOLN itself is an abstract
  supertype but with sig always there ready to use.  But
  there would be downcasts at certain points, when we
  traverse the lists.

  For solution sets we have

    #define INHERIT_SOLN_SET
      KHE_DRS_DOMINATOR		dominator;
      KHE_DRS_SOLN_SET_TYPE	type;


    typedef struct khe_drs_soln_list_rec {
      INHERIT_SOLN_SET
      ARRAY_KHE_DRS_SOLN	solns;
    } KHE_DRS_SOLN_LIST

  and so on.

  But what I have actually done today is greatly enhance the scope
  of type KHE_DRS_EXPANDER.  This has worked out very well.  It all
  needs a "functional audit", i.e. I need to trace through the
  function calls to make sure that the algorithms are still right.

  I've renamed the dom kinds, and renamed the option values that
  produce the dom kinds, and updated the documentation.  Some of
  the old names were very misleading.

16 November 2022.  Did an audit of the expand functions.  They look
  just great.

  Discovered that the must_assign and avail_resources fields of the
  expander were not being set correctly.  Sorted it out, including
  redoing KheDrsExpanderExpandBegin and KheDrsExpanderExpandEnd.

  Audited solve_prune_cost, as passed to KheDrsExpanderIsOpen; it
  seems fine.

17 November 2022.  Buried skip counts into the expander.

  Thinking about the resource assignment types and whether
  they can be made more comprehensible somehow.

     Possible new type names      Current names
     --------------------------------------------------------
     KHE_DRS_ASST_TO_TASK         KHE_DRS_FIXED_RESOURCE_ASST
     KHE_DRS_ASST_TO_TASK_CLASS   KHE_DRS_RESOURCE_ASST
     KHE_DRS_ASST_TO_SHIFT        (holds only the signature)
     --------------------------------------------------------

  Even if we do nothing more than change the type names, this will
  be an improvement.  Every KHE_DRS_ASST_TO_TASK has an enclosing
  KHE_DRS_ASST_TO_TASK_CLASS, and every KHE_DRS_ASST_TO_TASK_CLASS
  has an enclosing KHE_DRS_ASST_TO_SHIFT.  We already have a
  KHE_DRS_SHIFT_ASST type, so there is potential for confusion here.
  
  Also thought of

     Possible new type names      Current names
     --------------------------------------------------------
     KHE_DRS_SOLN_DAY		  KHE_DRS_SOLN
     KHE_DRS_SOLN_RESOURCE	  KHE_DRS_RESOURCE_ASST
     KHE_DRS_SOLN_SHIFT	          KHE_DRS_SHIFT_ASST
     --------------------------------------------------------

  but this does not seem to be such a good idea.  Arguably,
  you are a solution only if you contain all the assignments
  that make up a solution, as KHE_DRS_SOLN does when you take
  its prev_soln pointer into account.  The other two are more
  like fragments of solutions, things that go together to make
  solutions, than actual solutions.

  Made assignments into a new major category.

  Done KHE_DRS_ASST_TO_TASK, KHE_DRS_ASST_TO_TASK_CLASS,
  and KHE_DRS_ASST_TO_SHIFT.  Have clean compile.

18 November 2022.  Got rid of KheDrsResourceOnDaySignature by moving
  its sole call site into the expander and changing how that does it.
  This has allowed expand_assts_to_shifts to become an unsorted list
  of objects, including the former expand_free_asst_to_shift, which
  has been removed now.

  All signatures are inlined now; there is no signature free list.

  We are now only keeping assignment to shift objects that are
  actually used by at least one assignment to class object.  This
  helps when we transfer min cost across to the expander.

  Audited KHE_DRS_ASST_TO_TASK, KHE_DRS_ASST_TO_TASK_CLASS,
  KHE_DRS_ASST_TO_SHIFT, and related stuff.  All very good.

  Fixed a bug in KheDrsExpanderAddTasksAndSigsToSoln - it was
  sorting the assignments, which is incompatible with the rest.

19 November 2022.  Decided to carry on with the documentation.
  It's easy and it might throw up some issues.  In the middle
  of the Expansion section.

20 November 2022.  Carrying on with the documentation.  Audited
  what I did yesterday, made a few changes to the documentation
  and to the code, and noted a few problems that I will return
  to later.

21 November 2022.  Finished the pass through the documentation.
  But I still have to fix the problems I found along the way.

  Replaced the expander parameter of KheDrsShiftAsstTrieBuild with
  a solver parameter, to conform with the new policy of only passing
  the expander as a function argument in between calls to
  KheDrsExpanderExpandBegin and KheDrsExpanderExpandEnd.

  Replaced KheDrsExpanderExpandBegin by KheDrsExpanderMake, and
  KheDrsExpanderExpandEnd by KheDrsExpanderFree.  Now expander
  objects only exist while expansions are going on, and there
  can be independent expansions going on concurrently.

  Documented KheDrsExpanderMake and KheDrsExpanderFree.

22 November 2022.  Tidying up odds and ends thrown up by the
  revision of how expanders work.  Quite a few of them, but
  they all seem to be done now.  In fact the only dereferences
  of de now are to de->solver.  Audited everything, it's good.

  Thought about storing the open day index in the dominator.
  Then calls to KheDrsDominatorEvalSignature could omit it.
  But it would need to be reset every time we open a day,
  resource on day, or shift, and every type of expression uses
  the open day index but only one type uses the dominator.

  Started to update the documentation (again).  Just finished
  the expander section.

23 November 2022.  Finished revising the documentation (again).
  It covers 33 pages, although blank spaces at the bottoms of
  some pages would add up to four or five pages.  The whole
  implementation chapter is about 120 pages.

24 November 2022.  Finished auditing the documentation.  I've
  finally finalized the order of the sections, and established
  that days can be initialized for expansion before or after
  resources, although after is marginally better.

  Moved functions to their best sections and gave them their
  best names.

  Replaced all occurrences of KHE_DRS_RESOURCE_ON_DAY_SET
  by KHE_DRS_RESOURCE_SET.  It's simplified several things.

26 November 2022.  Reviewed the expansion documentation, again.
  Made a few tiny corrections, nothing much.  Did an off-site
  backup today.  Also revised the documentation of how to
  test (it was very hard to read) and started testing.

27 November 2022.  Modified KheDrsResourceExpandBegin so that
  it adds fixed assignments directly to the expander and builds
  unfixed_resources, the unfixed open resources.  Modified
  KheDrsSolnExpandByResources and KheDrsShiftBuildShiftAsstTrie
  so that they only access unfixed_resources now.

28 November 2022.  Audited what I wrote yesterday.  I was able
  to do some quite good tidying up.

  KheDrsResourceOnDayIsFixed now produces an assignment that
  goes directly into the expander.  But I'm not entirely
  clear that this does not disrupt the expand_used field of
  nearby task classes.  I believe it doesn't, but am I right?
  Proving it would require detailed arguments for the three
  cases that cause KheDrsResourceOnDayIsFixed to return true.

1 December 2022.  Took a couple of days of for gardening, also
  I've been thinking about handling shifts.  I've decided to
  do it properly, i.e. include all days of the shift in the
  solutions and costs and signatures, via a second list of
  children in INT_SUM_COST nodes, sorted by shift index.

  Started work on the new shift handling code.  So far I am
  adding the open_children_by_shift field to INT_SUM_COST, and
  initializing it correctly to the open children in increasing
  shift index order.  Next, I need to add the indexes into that
  array used by EvalSignature.

3 December 2022.  In the middle of losing a few days to chemo.
  I've worked through the whole file, looking at all places
  where open_children_by_day is used, and moving as much of
  the code as I conveniently can right now up to the new
  KHE_DRS_OPEN_CHILDREN submodule.

4 December 2022.  Added open_shift_index to DRS_SHIFT.  It is
  more what is wanted than index_in_cycle, which I have now
  removed, along with the equally irrelevant index_in_day.

5 December 2022.  Wrote KheDrsExprIntSumCostEvalShiftSignature, it
  seemed to go easily.  Rewrote KheDrsExprIntSumCostEvalSignature
  and KheDrsExprIntSumCostEvalShiftSignature (again) to follow the
  terminology currently used in the doc.  That went well.

6 December 2022.  Added "whole_tasks" attribute to the expander, now
  I have to use it.

7 December 2022.  I've written some code that appears to get past
  the "whole tasks" problem.  It needs careful thought and an audit.
  The cost includes the resource cost of the first assignment of a
  task on day to a resource on day, but that will be the same cost
  for every resource.  Leaving it out altogether would not harm the
  dominance testing, it would merely weaken the case for omitting
  some shift assignments altogether.

8 December 2022.  Looking things over, found that KheDrsExprOpen had
  not been adjusted for the new plan, doing that now.

9 December 2022.  Documented KHE_DRS_OPEN_CHILDREN.  I was able to
  refine the code based on what documenting it threw up.  I've also
  revised the documentation of expression opening up to KheDrsExprOpen
  itself.  All good so far.

10 December 2022.  Still working on KheDrsExprOpen.

  * A signature can be storable.

  * A signature can be retrievable, but only if it is for
    a d_k-complete solution.  At least, at present.

  * A signature can participate in a dominance test.

  So a "dominator" is really a "signature descriptor", saying
  what positions there are in the signature and what they can
  be used for.

    SIG_TYPE_DAY
      Signatures are storable, retrievable, and dom tests allowed

    SIG_TYPE_RESOURCE_ON_DAY
      Signatures are storable, but not retrievable (except by an
      append to another signature); dom tests are not supported

    SIG_TYPE_SHIFT
      Signatures are storable, but not retrievable, and dom tests
      are supported.

  KheDrsSigTypeMake(bool retrievable, bool dominance_allowed)

11 December 2022.  We can't muck around forever.  We're replacing
  KHE_DRS_DOMINATOR by KHE_DRS_SIGNER.  All done, in the doc and
  in the code, and audited.

12 December 2022.  Added a Boolean field to signers, saying whether
  they support dominance testing.  Resource on day signers don't.
  Done the resource monitors part of KheDrsExprOpen.  Shifts next.

13 December 2022.  Started work on the shift case of KheDrsExprOpen.
  Tidied up the functions that find dominance tests for int sum
  cost expressions.  Wrote KheDrsIntSumCostExprRequiresShiftDomTest,
  which finishes off KheDrsExprOpen.  The unassigned_child_count
  value used by dom tests does not include history after children.

  Updated the documentation, including the INT_SUM_COST section,
  which I've improved by reorganizing it somewhat.

  Audited how open shift ranges are initialized in external nodes.
  The range is not used in shift children at all.

15 December 2022.  Auditing everything.  I've just gone carefully
  through INT_SUM_COST and SIGNER.

  There is dominance testing in resource on day signers (during
  one-extra and two-extra selection), so I've reinstated it.

16 December 2022.  Looked at all calls to KheDrsSignerDominates,
  and found that I could simplify the first line of the uniform case.

  But most of the day went on carefully documenting what signers
  there are, what signatures there are, and which internal expressions
  and dominance tests are added to which signers.  Then I audited
  KheDrsExprOpen against this new documentation.

  The signature for the initial solution is not derived from any
  signer.  This is not a problem; the documentation explains it now.

17 December 2022.  Audited the new signer documentation again, and
  expanded it a bit.  What I have now should do.

  I've expanded the end of A.6.2 in response to this comment:
  "I've looked over the theory Appendix.  The relevant part is
  the last paragraph of A.6.2.  It speaks generally about
  extending S to S' and omits to say how the upper determinant
  u(m, S') is calculated.  So it is fine as is for shifts, but
  it might be better to add more detail to it at some stage."
  However the expanded version still does not speak specifically
  about shifts.  It's hard to see how it could, in that context.

18 December 2022.  Simplified KheDrsResourceAdjustSignatureCosts,
  using the fact that we can now assume that the assignment objects
  are sorted by increasing cost.

19 December 2022.  Sorted out fixed and free open resources, in
  the documentation first and in the code.  Audited it all.  Still
  have to use it in expanders.

21 December 2022.  Spent some time thinking about how to handle
  fixed assignments.  I've added a KHE_DRS_ASST_TO_TASK_SET type
  to hold sets of fixed assignments, and I'm using it and adding
  fixed assignments to the expander explicitly before anything
  else in the expansion.  Combine this with the way I'm handling
  free_resource_count and must_assign_count, and all is done now
  with the main expander.

  Perhaps I should make the expander later in KheDrsSolnExpand?
  I could handle fixed resources more easily that way.  No,
  because of the call to KheDrsExpanderOpenToExtraCost.

23 December 2022.  Spent yesterday and today on refereeing.  My
  next step is to sort out fixed assignments.

24 December 2022.  I've split KheDrsResourceExpandBegin into two.
  The first part adds the fixed assignments to the expander and
  returns the free resources.  The second part handles the free
  resources.  I'm doing this because fixed assignments can add
  to cost and so reduce the number of free assignments.

  Altered KheDrsExpanderMakeAndMeldShiftAsst so that it adds
  only non-fixed assts to tasks to the shift assignment object.
  So the expander holds all assignments, to give the best
  estimate of cost, but only the non-fixed ones are stored.

  I've looked carefully through the expand by resources and the
  expand by shifts code, and both seem to be fine and working
  correctly with fixed assignments, which go in first.  I just
  need to get the shift assignment code working, and also look
  over one-extra and two-extra selection (they may have decayed).

  Added KheDrsTaskClassExpandBegin and KheDrsTaskClassExpandEnd
  to a new "task class - expansion" submodule.

25 December 2022.  Covid struck our household this morning,
  although not me personally (yet).

  I've replaced expand_used by expand_prev_unfixed in task
  classes.  I'm not completely sure that it is needed, but
  it might be, especially on reruns, so I'm doing it anyway.

  Rewrote the documentation for beginning and ending expansion
  in days, shifts, task class, and tasks.  All good.

26 December 2022.  Audited the expander documentation and brought
  it up to date.  Changed the array of asst to task objects in
  shift assignment objects into a KHE_DRS_ASST_TO_TASK_SET, and
  updated the documentation for that, but decided not to do it
  for the ones in the expander.  Added a scratch expander so I
  don't need to create and free one all the time.

27 December 2022.  Sorted out some confusion over whether a
  KHE_DRS_ASST_TO_TASK_CLASS object can be NULL.

  Found and fixed a nasty bug in shift assignment construction:
  each resource was iterating over all its assignments, not
  over all its assignments for a given shift.

28 December 2022.  Audited shift assignment construction code.
  It seems to be correct, insofar as I can follow it through
  the brain fog I'm suffering from the heat and lack of sleep.
  No wonder - positive RAT test today.

31 December 2022.  I've been taking a COVID-induced holiday,
  although my symptoms are very mild.  Today I reworked some
  of the documentation, "initializing resources for expansion".

1 January 2023.  Did some more documentation auditing.  I'm
  now ready to audit "Initializing shift assignment tries for
  expansion" again.

======================================================
STOP!  STOP!  STOP!  STOP!  STOP!  STOP!  STOP!  STOP!
======================================================

  To Do
  =====

    Did breaking up resource expand begin into two stages
    actually achieve anything?  KheDrsExpanderOpenToExtraCost
    is called but does not seem to be affected by the breakup.

    We should grow the number of free resources from 0,
    not decrease it from the number of open resources.

    KheDrsAsstToTaskAssignsFreeResource - better to work
    out dr separately (because it is needed lower down)
    and simplify this function to just looking up its role.

    Off-site backup.

    KheDrsResourceHasAssignment better placed in the
    KHE_DRS_ASST_TO_TASK_CLASS submodule?

    Look over one-extra and two-extra selection (they may have decayed).

    Some principles:

    In KheDrsDayExpandBegin, verify that the excess resources
    are handling fixed assignments correctly.  This may depend
    on opening resources for expansion before days.  If so,
    that dependency needs to be documented.  Actually there
    is a problem, because KheDrsExpanderAddAsstToTask assumes
    that dt->expand_must_assign is true, when it seems that
    during fixed resource handling it has no value at all.
    The call to KheDrsExpanderContainsAsstToTask seems to have
    been put in to fix this problem, but it doesn't fix it well.

    Time to audit the whole shift assignment creation and dom
    checking code, including "whole tasks" expansion.

    KheDrsExprAddToShift is still failing in the same way.
    The problem is with grouped tasks, which shift(s) do they
    enrol themselves in?  None, arguably, if they are fixed.
    But we do want their signatures even if they are fixed.

    Started testing, current bug is

      KheDrsShiftAddOpenExpr internal error:  day range 4 - 4, day 3

    which seems to indicate that some expression is enrolling
    itself into a shift on the wrong day.  Could it be to do
    with the fact that the solution is d3-complete plus a shift
    on d4?  I might be getting confused between these two days.
    The shift is on 1Fri, it should not enrol into a day 3 shift.

      KheDrsSignatureDominates internal error 1 (count 0 != count 2)

    The 0 is ddm->dom_tests which is correct because the instance has
    no Limit Resources constraints; the 2 is sig1->states which is wrong
    here.  "KheDrsExprOpenDayIsLast false" seems to be the root cause.
    "open_day_range 4 - 4, next_di 3" so we've been called on a wrong
    day.  The open day range is correct because the monitor Id is
    1Fri, which is indeed day 4.  So how did we get called on day 3?

    EvalSignature:  where is it presented, do the calls to it
    make sense to the reader, e.g. in KheDrsAsstToShiftMake?

    Expansion by shifts is done and dusted.  I'm now casting
    around for further improvements to make.

    Make the solver return early (with failure) if time runs out.

    Signature value adjustment may need a rethink for sequence
    monitors.

    May need to revisit the current plan of always returning when
    the available cost goes negative.  This is because sequence
    monitors can contribute a positive amount to available cost.
    At any rate we should do some testing to see which is faster.

    Good idea:  compare old dom test with new dom test, and
    if there are cases where the old test succeeds and the
    new one does not, look into it.  Also vice versa.

    If we want to combine ejection chains with dynamic programming,
    it might actually be easier to add ejection chain code to the
    dynamic programming module.  Limit task sets to the ones that
    the resources were freed from.  Could do that now, actually.

    What about a solver that swaps around the assigned shifts,
    without assigning or unassigning any resource, with the
    aim of getting the number of consecutive same shifts right.
    Is that a tractable problem?  Surely ejection chains do that?

    Look into how the resource assignment invariant interacts
    with the new "rs" option.

    It seems to be time to do some serious testing of the VLSN solver
    and compare what we get with what Legrain got.  My paper says he
    got 1695, his paper (tt/patat21/dynamic_papers/legrain.pdf) says
    1685.  My own best result from my own paper was 1835.

    Legrain's running time is m x 6n + 60, where m is the number of
    weeks and n is the number of nurses.  For the test instance,
    m = 4 and n = 30, so this is 4 x 6 x 30 + 60 = 13 minutes.

    Testing ./doit in tt/nurse/solve.

    Withdraw old attempts at task classes (see first paragraph
    of task classes documentation).

    Reference in paper I refereed to dynamic programming solver?

    Speaking generally, we now have two new solvers to play with:
    the single resource solver, and the cluster minimum solver.
    Our mission is to make the best use we can of both.  We can
    run the cluster minimum solver once at the start and have its
    results permanently used throughout the rest of the solve.  And
    we can use KheSingleResourceSolverBest in conjunction with a
    balance solver to select a best solution from the single
    resource solver, and adopt that solution.  But when should we
    run single resource solving, and which resource(s) should we
    select for single resource solving?  For example:

    * Run single-resource solving on a fixed percentage of the
      resources (with highest workload limits) before time sweep,
      and then omit those resources from the time sweep.

    * Find optimal timetables for several resources over a subset
      of the interval, and use that as the basis for a VLSN search.

    Explore possible uses for the now-working cluster minimum
    solver.  Could it be run just before time sweep?  Could
    the changed minimum limits remain in place for the entire
    solve?  Also look at the solutions we are getting now from
    single resource assignment.  If one resource is already
    assigned, does that change the solve for the others?

    Make the cluster minimum solver take account of history.

    OK, what about this?  Use "extended profile grouping" to group all
    tasks into runs of tasks of the same shift type and domain.  Then
    use resource packing (largest workload resources first) to pack
    the runs into the resources.  Finish off with ejection chains.
    This to replace the current first stage.  Precede profile grouping
    by combinatorial grouping, to get weekend tasks grouped together.  
    Keep a matching at each time, so that unavailable times of other
    resources are taken into account, we want the unassigned tasks at
    every time to be assignable to the unpacked resources at that time.
    At least it's different!

    After INRC2-4-030-1-6291 is done, INRC2-4-035-0-1718 would be good to
    work on.  The current results are 21% worse, giving plenty to get into.

    Event timetables still to do.  Just another kind of dimension?
    But it shows meets, not tasks.

    Ideas:

    * Some kind of lookahead during time sweep that ensures resources
      get the weekends they need?  Perhaps deduce that the max limit
      implies a min limit, and go from there?

    * Swapping runs between three or more resources.  I tried this
      but it seems to take more time than it is worth; it's better
      to give the extra time to ejection chains

    * Ejection beams - K ejection chains being lengthened in
      parallel, if the number of unrepaired defects exceeds K
      we abandon the repair, but while it is less we keep going
      Tried this, it has some interest but does not improve things.

    * Hybridization with simulated annealing:  accept some chains
      that produce worse solutions; gradually reduce the temperature.

  Decided to just pick up where I left off, more or less, and go to
  work on INRC2-4-030-1-6291.  I'm currently solving in just 5.6
  seconds, so it makes a good test.

  Fun facts about INRC2-4-030-1-6291
  ----------------------------------

  * 4 weeks

  * 4 shifts per day:  Early (1), Day (2), Late (3), and Night (4) 
    The number of required ones varies more or less randomly; not
    assigning one has soft cost 30.

  * 30 Nurses:

       4 HeadNurse:  HN_0,  ... , HN_3
      13 Nurse:      NU_4,  ... , NU_16
       8 Caretaker:  CT_17, ... , CT_24
       5 Trainee:    TR_25, ... , TR_29

    A HeadNurse can also work as a Nurse, and a Nurse can also work
    as a Caretaker; but a Caretaker can only work as a Caretaker, and
    a Trainee can only work as a Trainee.  Given that there are no
    limit resources constraints and every task has a hard constraint
    preferring either a HeadNurse, a Nurse, a Caretaker, or a Trainee,
    this makes Trainee assignment an independent problem.

  * 3 contracts: Contract-FullTime (12 nurses), Contract-HalfTime
    (10 nurses), Contract-PartTime (8 nurses).  These determine
    workload limits of various kinds (see below).  There seems
    to be no relationship between them and nurse type.

  * There are unavailable times (soft 10) but they are not onerous

  * Unwanted patterns: [L][ED], [N][EDL], [D][E] (hard), so these
    prohibit all backward rotations.

  * Complete weekends (soft 30)

  * Contract constraints:                   Half   Part   Full    Wt
    ----------------------------------------------------------------
    Number of assignments                   5-11   7-15  15-20*   20
    Max busy weekends                          1      2      2    30
    Consecutive same shift days (Early)      2-5    2-5    2-5    15
    Consecutive same shift days (Day)       2-28   2-28   2-28    15
    Consecutive same shift days (Late)       2-5    2-5    2-5    15
    Consecutive same shift days (Night)      3-5    3-5    3-5    15
    Consecutive free days                    2-5    2-4    2-3    30
    Consecutive busy days                    2-4    3-5    3-5    30
    ----------------------------------------------------------------
    *15-20 is notated 15-22 but more than 20 is impossible.

  Currently giving XUTT a rest for a while.  Here is its to do
  list, prefixed by + characters:

  +Can distinct() be used for distinct times?  Why not?  And also
  +using it for "same location" might work.

  +I've finished university course timetabling, except for MaxBreaks
  +and MaxBlock, which I intend to leave for a while and ponder over
  +(see below).  I've also finished sports scheduling except for SE1
  +"games", which I am waiting on Bulck for but which will not be a
  +problem.

  +MaxBreaks and MaxBlock
  +----------------------

    +These are challenging because they do the sorts of things that
    +pattern matching does (e.g. idle times), but the criterion
    +which determines whether two things are adjacent is different:

      +High school timetabling - adjacent time periods
      +Nurse rostering - adjacent days
      +MaxBreaks and MaxBlock - intervals have gap of at most S.

    +It would be good to have a sequence of blocks to iterate over,
    +just like we have some subsequences to iterate over in high
    +school timetabling and nurse rostering.  Then MaxBreaks would
    +utilize the number of elements in the sequence, and MaxBlock
    +would utilize the duration of each block.

    +We also need to allow for future incorporation of travel time 
    +into MaxBreaks and MaxBlock.  Two events would be adjacent if
    +the amount of time left over after travelling from the first
    +to the second was at most S.

    +Assuming a 15-week semester and penalty 2:

    +MaxBreaks(R, S):

	+<Tree val="sum|15d">
	    +<ForEach v="$day" from="Days">
		+<Tree val="sum:0-(R+1)|2">
		    +<ForEachBlock v="$ms" gap="S" travel="travel()">
			+<AtomicMeetSet e="E" t="$day">
			+<Tree val="1">
		    +</ForEachBlock>
		+</Tree>
	    +</ForEach>
	+</Tree>

    +MaxBlock(M, S):

	+<Tree val="sum|15d">
	    +<ForEach v="$day" from="Days">
		+<Tree val="sum:0-M|2">
		    +<ForEachBlock v="$ms" gap="S" singles="no" travel="travel">
			+<AtomicMeetSet e="E" t="$day">
			+<Tree val="$ms.span:0-M|1s">
		    +</ForEachBlock>
		+</Tree>
	    +</ForEach>
        +</Tree>

    +Actually it might be better if each iteration produced a meet set.
    +We could then ask for span and so forth as usual.  There is also
    +a connection with spacing(a, b).  In fact it would be good to
    +give a general expression which determines whether two
    +chronologically adjacent meets are in the same block.
    +Then we could use "false" to get every meet into a separate
    +block, and then spacing(a, b) would apply to each pair of
    +adjacent blocks in the ordering.  If "block" has the same
    +type as "meet set", we're laughing.

    +I'll let this lie fallow for a while and come back to it.

  +Rather than sorting meets and defining cost functions which
  +are sums, can we iterate over the sorted meets?

  +The ref and expr attributes of time sequences and event sequences
  +do the same thing.

  +There is an example of times with attributes in the section on
  +weighted domain constraints.  Do we want them?  How do they fit
  +with time pattern trees?  Are there weights for compound times?

  +Moved history from Tree to ForEachTimeGroup.  This will be
  +consistent with pattern matching, and more principled, since
  +history in effect extends the range of the iterator.  But
  +what to do about general patterns?  We need to know how each
  +element of the pattern matches through history.

  +Could use tags to identify specific task sets within patterns.

  Install the new version of HSEval on web site, but not until after
  the final PATAT 2020 deadline.

  In the CQ14-13 table, I need to see available workload in minutes.

  Fun facts about instance CQ14-13
  --------------------------------

  * A four-week instance (1Mon to 4Sun) with 18 times per day:

      a1 (1),  a2 (2),  a3 (3),  a4 (4),  a5 (5),
      d1 (6),  d2 (7),  d3 (8),  d4 (9),  d5 (10),
      p1 (11), p2 (12), p3 (13), p4 (14), p5 (15),
      n1 (16), n2 (17), n3 (18)

    There are workloads, presumably in minutes, that vary quite a bit:

      a1 (480),  a2 (480),  a3 (480),  a4 (600),  a5 (720),
      d1 (480),  d2 (480),  d3 (480),  d4 (600),  d5 (720),
      p1 (480),  p2 (480),  p3 (480),  p4 (600),  p5 (720),
      n1 (480),                        n2 (600),  n3 (720)

    480 minutes is an 8-hour shift, 720 minutes is 12 hours.

  * 120 resources, with many hard preferences for certain shifts:

      Preferred-a1 Preferred-a2 Preferred-a3 Preferred-a4 Preferred-a5
      Preferred-d1 Preferred-d2 Preferred-d3 Preferred-d4 Preferred-d5
      Preferred-p1 Preferred-p2 Preferred-p3 Preferred-p4 Preferred-p5
      Preferred-n1 Preferred-n2 Preferred-n3

    although most resources have plenty of choices from this list.
    Anyway this leads to a huge number of prefer resources constraints.

  * There are also many avoid unavailable times constraints, some for
    whole days, many others for individual times; hard and soft.

  * Unwanted patterns (hard).  In these patterns, a stands for
    [a1a2a3a4a5] and so on.

      [d4][a]
      [p5][adp4-5]
      [n1][adp]
      [n2-3][adpn3]
      [d1-3][a1-4]
      [a5d5p1-4][ad]

    This is basically "day off after a sequence of night shifts",
    with some other stuff that probably matters less; a lot of it
    is about the 480 and 720 minute shifts.

  * MaxWeekends (hard) for most resources is 2, for some it is 1 or 3.

  * MaxSameShiftDays (hard) varies a lot, with fewer of the long
    workload shifts allowed.  NB this is not consecutive, this is
    total.  About at most 10 of the shorter, 3 of the longer.
    Doesn't seem very constraining, given that typical workloads
    are 15 or 16 shifts.

  * Many day or shift on requests, soft with varying weights (1-3).

  * Minimum and maximum workload limits in minutes (hard), e.g.

      Minutes           480-minute shifts
      -----------------------------------------------------------
      3120 - 3840
      4440 - 5160
      7440 - 8160        15.5 - 17.0
      7920 - 8640        16.5 - 18.0

    The last two ranges cover the great majority of resources.
    These ranges are quite tight, especially for hard constraints.

  * MinConsecutiveFreeDays 2 (hard) for most resources, 3 (hard)
    for a few.

  * MaxConsecutiveBusyDays 5 (hard) for most resources, 6 (hard)
    for a few.

  * MinConsecutiveBusyDays 2 (hard), for all or most resources.

  Decided to work on CQ14-13 for a while, then tidy up, rerun,
  and submit.

  What does profile grouping do when the minimum limits are
  somewhat different for different resources, and thus spread
  over several constraints?

  INRC1-ML02 would be a good test.  It runs fast and the gap is
  pretty wide at the moment.  Actually I worked on it before (from
  8 November 2019).  It inspired KhePropagateUnavailableTimes.

  Fun facts about INRC1-ML02
  --------------------------

    * 4 weeks 1Fri to 4Thu

    * 4 shifts per day: E (1), L (2), D (3), and N (4).  But there are
      only two D shifts each day, so this is basically a three-shift
      system of Early, Late, and Night shifts.

    * 30 Nurses:
  
        Contract-0  Nurse0  - Nurse7
        Contract-1  Nurse8  - Nurse26
        Contract-2  Nurse27 - Nurse29

    * Many day and shift off requests, all soft 1 but challenging.
      I bet this is where the cost is incurred.

    * Complete weekends (soft 2), no night shift before free
      weekend (soft 1), identical shift types during weekend (soft 1),
      unwanted patterns [L][E], [L][D], [D][N], [N][E], [N][D],
      [D][E][D], all soft 1

    * Contract constraints         Contract-0    Contract-1   Contract-2
      ----------------------------------------------------------------
      Assignments                    10-18        6-14          4-8
      Consecutive busy weekends       2-3     unconstrained     2-3
      Consecutive free days           2-4         3-5           4-6
      Consecutive busy days           3-5         2-4           3-4
      ----------------------------------------------------------------

      Workloads are tight, there are only 6 shifts to spare, or 8 if
      you ignore the overloads in Nurse28 and Nurse29, which both
      GOAL and KHE18x8 have, so presumably they are inevitable.


  Do something about constraints with step cost functions, if only
  so that I can say in the paper that it's done.

  In INRC2-4-030-1-6291, the difference between my 1880 result and
  the LOR17 1695 result is about 200.  About 100 of that is in
  minimum consecutive same shift days defects.  Max working weekends
  defects are another problem, my solution has 3 more of those
  than the LOR17 solution has; at 30 points each that's 90 points.
  If we can improve our results on these defects we will go a long
  way towards closing the gap.

  Grinding down INRC2-4-030-1-6291 from where it is now.  It would
  be good to get a better initial solution from time sweep than I am
  getting now.  Also, there are no same shift days defects in the
  LOR17 solution, whereas there are 

  Perhaps profile grouping could do something unconventional if it
  finds a narrow peak in the profile that really needs to be grouped.

  What about an ejection chain repair, taking the current runs
  as indivisible?

  My chances of being able to do better on INRC2-4-030-1-6291
  seem to be pretty slim.  But I really should pause and make
  a serious attack on it.  After that there is only CQ to go,
  and I have until 30 January.  There's time now and if I don't
  do it now I never will.

  Better to not generate contract (and skill?) resource groups if
  not used.

  Change KHE's general policy so that operations that change
  nothing succeed.  Having them fail composes badly.  The user
  will need to avoid cases that change nothing.

  Are there other modules that could use the task finder?
  Combinatorial grouping for example?  There are no functions
  in khe_task.c that look like task finding, but there are some
  in khe_resource_timetable_monitor.c:

    KheResourceTimetableMonitorTimeAvailable
    KheResourceTimetableMonitorTimeGroupAvailable
    KheResourceTimetableMonitorTaskAvailableInFrame
    KheResourceTimetableMonitorAddProperRootTasks

  KheTaskSetMoveMultiRepair phase variable may be slow, try
  removing it and just doing everything all together.

  Fun facts about COI-Musa
  ------------------------

  * 2 weeks, one shift per day, 11 nurses (skills RN, LPN, NA)

  * RN nurses:  Nurse1, Nurse2, Nurse3,
    LPN nurses: Nurse4, Nurse5, 
    NA nurses:  Nurse6, Nurse7, Nurse8, Nurse9, Nurse10, Nurse11

  Grinding down COI-HED01.  See above, 10 October, for what I've
  done so far.

  It should actually be possible to group four M's together in
  Week 1, and so on, although combinatorial grouping only tries
  up to 3 days so it probably does not realize this.

  Fun facts about COI-HED01
  -------------------------

    * 31 days, 5 shifts per day: 1=M, 2=D, 3=H, 4=A, 5=N

    * Weekend days are different, they use the H shift.  There
      is also something peculiar about 3Tue, it also uses the
      H shift.  It seems to be being treated like a weekend day.
      This point is reflected in other constraints, which treat
      Week 3 as though it had only four days.

    * All demand expressed by limit resources constraints,
      except for the D shift, which has two tasks subject
      to assign resource and prefer resources constraints.
      The other shifts vary between about 7 and 9 tasks.  But
      my new converter avoids all limit resources constraints.

    * There are 16 "OP" nurses and 4 "Temp" nurses.
      Three nurses have extensive sequences of days off.
      There is one skill, "Skill-0", but it contains the
      same nurses as the OP nurses.

    * The constraints are somewhat peculiar, and need attention
      (e.g. how do they affect combinatorial grouping?)
    
        [D][0][not N]  (Constraint:1)
          After a D, we want a day off and then a night shift (OP only).
	  Only one nurse has a D at any one time, so making this should
	  not be very troublesome.

	[not M][D]  (Constraint:2)
	  Prefer M before D (OP only), always seems to get ignored,
	  even in the best solutions.  This is because during the
	  week that D occurs, we can't have a week full of M's.
	  So really this constraint contradicts the others.

	[DHN][MDHAN]  (Constraint:3)
	  Prefer day off after D, H, or N.  Always seems to be
	  satisfied.  Since H occurs only on weekends, plus 3Tue,
	  each resource can work at most one day of the weekend,
	  and if that day is Sunday, the resource cannot work
	  M or A shifts the following week (since that would
	  require working every day).  Sure enough, in the
	  best solution, when an OP nurse works an H shift on
	  a Sunday, the following week contains N shifts and
	  usually a D shift.  And all of the H shifts assigned
	  to Temp nurses are Sunday or 3Tue ones.

	Constraint:4 says that Temp nurses should take H and
	D shifts only.  It would be better expressed by a
	prefer resources constraint but KHE seems happy
	enough with it.

	Constraint:5 says that assigning any shift at all to
	a Temp nurse is to be penalized.  Again, a prefer
	resources constraint would have been better, but at
	present both KHE and the best solution assign 15 shifts
	to Temp nurses, so that's fine.

	The wanted pattern is {M}{A}{ND}{M}{A}{ND}..., where
	{X} means that X only should occur during a week.
	This is for OP nurses only.  It is expressed rather
	crudely:  if 1 M in Week X, then 4 M in Week X.
	This part of it does not apply to N, however; it says
	"if any A in Week X, then at least one N in Week X+1".
	So during N weeks the resource usually has less than
	4 N shifts, and this is its big chance to take a D.

	OP nurses should take at least one M, exactly one D,
	at least one H, at most 2 H, at least one A, at least
	one N.  These constraints are not onerous.

    * Assign resource and prefer resources constraints specify:

        - There is one D shift per day

    * Limit resources constraints specify 

        Weekdays excluding 3Tue

        - Each N shift must have exactly 2 Skill-0 nurses.

	- Each M shift and each A shift must have exactly 4
	  Skill-0 nurses

	- There are no H shifts

	Weekend days, including 3Tue

	- Each H shift must have at least 2 Skill-0 nurses

	- Each H shift must have exactly 4 nurses altogether

	- There are no M, A, or N shifts on 3Tue

	- There are no M, A, or N shifts on weekend days

    * The new converter is expressing all demands with assign
      resource and prefer resources constraints, as follows:

      D shifts:

        <R>NA=s1000:1</R>
	<R>A=s1000:1</R>

	So one resource, any skill.

      H shifts (weekends and 3Tue):

        <R>NA=s1000+NW0=s1000:1</R>
	<R>NA=s1000+NW0=s1000:2</R>
	<R>NA=s1000:1</R>
	<R>NA=s1000:2</R>
	<R>A=s1000:1</R>

	So 2 Skill-0 and 2 arbitrary, as above

      M and A shifts (weekdays not 3Tue):

        <R>NA=s1000+NW0=s1000:1</R>
	<R>NA=s1000+NW0=s1000:2</R>
	<R>NA=s1000+NW0=s1000:3</R>
	<R>NA=s1000+NW0=s1000:4</R>
	<R>W0=s1000:1</R>
	<R>W0=s1000:2</R>
	<R>W0=s1000:3</R>
	<R>W0=s1000:4</R>
	<R>W0=s1000:5</R>

	So exactly 4 Skill-0, no limits on Temp nurses

      N shifts (weekday example)

        <R>NA=s1000+NW0=s1000:1</R>
	<R>NA=s1000+NW0=s1000:2</R>
	<R>W0=s1000:1</R>
	<R>W0=s1000:2</R>
	<R>W0=s1000:3</R>
	<R>W0=s1000:4</R>
	<R>W0=s1000:5</R>

      Exactly 2 Skill-0, no limits on Temp nurses.

  It would be good to have a look at COI-HED01.  It has
  deteriorated and it is fast enough to be a good test.
  Curtois' best is 136 and KHE18x8 is currently at 183.
  A quick look suggests that the main problems are the
  rotations from week to week.

  Back to grinding down CQ14-05.  I've fixed the construction
  problem but with no noticeable effect on solution cost.

  KheClusterBusyTimesConstraintResourceOfTypeCount returns the
  number of resources, not the number of distinct resources.
  This may be a problem in some applications of this function.

  Fun facts about CQ14-05
  -----------------------

    * 28 days, 2 shifts per day (E and L), whose demand is:

           1Mon 1Tue 1Wed 1Thu 1Fri 1Sat 1Sun 2Mon 2Tue 2Wed 2Thu
        ---------------------------------------------------------
        E   5    7    5    6    7    6    6    6    6    6    5
        L   4    4    5    4    3    3    4    4    4    6    4
        ---------------------------------------------------------
        Tot 9   11   10   10   10    9   10   10   10   12    9

      Uncovered demands (assign resources defects) make up the
      bulk of the cost (1500 out of 1543).  Most of this (14 out
      of 15) occurs on the weekends.

    * 16 resources named A, B, ... P.  There is a Preferred-L
      resource group containing {C, D, F, G, H, I, J, M, O, P}.
      The resources in its complement, {A, B, E, K, L, N}, are
      not allowed to take late shifts.

    * Max 2 busy weekends (max 3 for for resources K to P)

    * Unwanted pattern [L][E]

    * Max 14 same-shift days (not consecutive).  Not hard to
      ensure given that resource workload limits are 16 - 18.

    * Many day or shift on requests.  These basically don't
      matter because they have low weight and my current best
      solution has about the same number of them as Curtois'

    * Workload limits (all resources) min 7560, max 8640
      All events (both E and L) have workload 480;
      7560 / 480 = 15.7, 8640 / 480 = 18.0, so every resurce
      needs between 16 and 18 shifts.  The Avail column agrees.

    * Min 2 consecutive free days (min 3 for resources K to P)

    * Max 5 consecutive busy days (max 6 for resources K to P)

    * Curtois' best is 1143.  This represents 2 fewer unassigned
      shifts (costing 100 each) and virtually the same other stuff.

  Try to get CQ14-24 to use less memory and produce better results.
  But start with a smaller, faster CQ14 instance:  CQ14-05, say.

  In Ozk*, there are two skill types (RN and Aid), and each
  nurse has exactly one of those skills.  Can this be used to
  convert the limit resources constraints into assign resource
  and prefer resources constraints?

  Grinding down COI-BCDT-Sep in general.  I more or less lost
  interest when I got cost 184 on the artificial instance, but
  this does include half-cycle repairs.  So more thought needed.
  Could we add half-cycle repairs to the second repair phase
  if the first ended quickly?

  KheCombSolverAddProfileGroupRequirement could be merged with
  KheCombSolverAddTimeGroupRequirement if we add an optional
  domain parameter to KheCombSolverAddTimeGroupRequirement.

  Fun facts about COI-BCDT-Sep
  ----------------------------

    * 4 weeks and 2 days, starting on a Wednesday

    * Shifts: 1 V (vacation), 2 M (morning), 3 A (afternoon), 4 N (night).

    * All cover constraints are limit resources constraints.  But they
      are quite strict and hard.  Could they be replaced by assign
      resource constraints?  (Yes, they have been.)

	  Constraint            Shifts               Limit    Cost
	  --------------------------------------------------------
          DemandConstraint:1A   N                    max 4      10
	  DemandConstraint:2A   all A; weekend M     max 4     100
	  DemandConstraint:3A   weekdays M           max 5     100
	  DemandConstraint:4A   all A, N; weekend M  max 5    hard
	  DemandConstraint:5A   weekdays M           max 6    hard
	  DemandConstraint:6A   all A, N; weekend M  min 3    hard
	  DemandConstraint:7A   all N                min 4      10
	  DemandConstraint:8A   all A; weekend M     min 4     100
	  DemandConstraint:9A   weekday M            min 4    hard
	  DemandConstraint:10A  weekday M            min 5     100
	  --------------------------------------------------------

      Weekday M:   min 4 (hard), min 5 (100), max 5 (100), max 6 (hard),
      Weekend M:   min 3 (hard), min 4 (100), max 4 (100), max 5 (hard) 
      All A:       min 3 (hard), min 4 (100), max 4 (100), max 5 (hard)
      All N:       min 3 (hard), min 4 (10),  max 4 (10),  max 5 (hard)

    * There are day and shift off constraints, not onerous

    * Avoid A followed by M

    * Night shifts are to be assigned in blocks of 3, although
      a four block is allowed to avoid fri N and sat free.  There
      are hard constraints requiring at least 2 and at most 4
      night shifts in a row.

    * At least six days between sequences of N shifts; the
      implementation here could be better, possibly.

    * At least two days off after five consecutive shifts

    * At least two days off after night shift

    * Prefer at least two morning shifts before a vacation period and
      at least one night shift afterwards

    * Between 4 and 8 weekend days

    * At least 10 days off

    * 5-7 A (afternoon) shifts, 5-7 N (night) shifts

    * Days shifts (M and A, taken together) in blocks of exactly 3

    * At most 5 working days in a row.

  Work on COI-BCDT-Sep, try to reduce the running time.  There are
  a lot of constraints, which probably explains the poor result.

  Should we limit domain reduction at the start to hard constraints?
  A long test would be good.

  In khe_se_solvers.c, KheAddInitialTasks and KheAddFinalTasks could
  be extended to return an unassign_r1_ts task set which could then be
  passed on to the double repair.  No great urgency, but it does make
  sense to do this.  But first, let's see whether any instances need it.

  Also thought of a possibility of avoiding repairs during time sweep,
  when the cost blows out too much.  Have to think about it and see if
  it is feasible.

  Take a close look at resource matching.  How good are the
  assignments it is currently producing?  Could it do better?

  Now it is basically the big instances, ERRVH, ERMGH, and MER
  that need attention.  Previously I was working on ERRVH, I
  should go back to that.

  Is lookahead actually working in the way I expect it to?
  Or is there something unexpected going on that is preventing
  it from doing what it has the potential to do?

  UniTime requirements not covered yet:

    Need an efficient way to list available rooms and their
    penalties.  Nominally this is done by task constraints but
    something more concise, which indicates that the domain
    is partitioned, would be better.

    Ditto for the time domain of a meet.

    SameStart distribution constraint.  Place all times
    with the same start time in one time group, have one
    time group for each distinct starting time, and use
    a meet constraint with type count and eval="0-1|...".

    SameTime is a problem because there is not a simple
    partition into disjoint sets of times.  Need some
    kind of builtin function between pairs of times, but
    then it's not clear how this fits in a meet set tree.

    DifferentTime is basically no overlap, again we seem
    to need a binary attribute.

    SameDays and SameWeeks are cluster constraints, the limit
    would have to be extracted from the event with the largest
    number of meets, which is a bit dodgy.

    DifferentDays and DifferentWeeks just a max 1 on each day
    or week.

    Overlap and NotOverlap: need a binary for the amount of
    overlap between two times, and then we can constrain it
    to be at least 1 or at most 0.  NB the distributive law

       overlap(a+b, c+d) = overlap(a, c) + overlap(a, d)
         + overlap(b, c) + overlap(b, d)

    but this nice property is not going to hold for all
    binary attributes.

    Precedence: this is the order events constraint, with
    "For classes that have multiple meetings in a week or
    that are on different weeks, the constraint only cares
    about the first meeting of the class."  No design for
    this yet.

    WorkDay(S): "There should not be more than S time slots
    between the start of the first class and the end of the
    last class on any given day."  This is a kind of avoid
    idle times constraint, applied to events rather than to
    resources (which for us is a detail).
      One task or meet set per day, and then a special function
    (span or something) to give the appropriate measure.  But
    how do you define one day?  By a time group.

    MinGap(G): Any two classes that are taught on the same day
    (they are placed on overlapping days and weeks) must be at
    least G slots apart.  Not sure what to make of this.
    I guess it's overlap(a, b, extension) where extension
    applies to both a and b.

    MaxDays(D): "Given classes cannot spread over more than D days
    of the week".  Just a straight cluster constraint.

    MaxDayLoad(S): "Given classes must be spread over the days
      of the week (and weeks) in a way that there is no more
      than a given number of S time slots on every day."  Just
      a straight limit busy times constraint, measuring durations.
      But not the full duration, rather the duration on one day.

      This is one of several indications that we cannot treat
      a non-atomic time as a unit in all cases.

    MaxBreaks(R,S): "MaxBreaks(R,S) This constraint limits the
      number of breaks during a day between a given set of classes
      (not more than R breaks during a day). For each day of week
      and week, there is a break between classes if there is more
      than S empty time slots in between."  A very interesting
      definition of what it means for two times to be consecutive.

    MaxBlock(M,S): "This constraint limits the length of a block
      of consecutive classes during a day (not more than M slots
      in a block). For each day of week and week, two consecutive
      classes are considered to be in the same block if the gap
      between them is not more than S time slots."  Limit active
      intervals, interpreted using durations rather than times.

  A resource r is busy at some time t if that time overlaps with
  any interval in any meet that r is attending.

  Need a way to define time *groups* to take advantage of symmetries.
  e.g. 1-15{MWF}3 = {1-15M3, 1-15W3, 1-15F3}.  All doubles:
  [Mon-Fr][12 & 23 & 45 & 67 & 78] or something.
  {MWF:<time>} or something.  But what is the whole day anyway?
  All intervals, presumably. {1-15:{MTWRF:1-8}

  See 16 April 2019 for things to do with the XUTT paper.

  It's not clear at the moment how time sweep should handle
  rematching.  If left as is, without lookahead, it might
  well undo all the good work done by lookahead.  But to
  add lookahead might be slow.  Start by turning it off:
  rs_time_sweep_rematch_off=true.  The same problem afflicts
  ejection chain repair during time sweep.  Needs thought.
  Can the lookahead stuff be made part of the solution cost?
  "If r is assigned t, add C to solution cost".  Not easily.
  It is like a temporary prefer resources monitor.

  Here's an idea for a repair:  if a sequence is too short, try
  moving it all to another resource where there is room to make
  it longer.  KheResourceUnderloadAugment will in fact do nothing
  at all in these cases, so we really do need to do something,
  even an ejecting move on that day.

  Working over INRC2-4-030-1-6753 generally, trying to improve
  the ejection chain repairs.  No luck so far.

  Resource swapping is really just resource rematching, only not
  as good.  That is, unless there are limit resources constraints.

  The last few ideas have been too small beer.  Must do better!
  Currently trying to improve KHE18's solutions to INRC2-4-035-2-8875.xml:

    1 = Early, 2 = Day, 3 = Late, 4 = Night
    FullTime: max 2 weekends, 15-22 shifts, consec 2-3 free 3-5 busy
    PartTime: max 2 weekends,  7-15 shifts, consec 2-5 free 3-5 busy
    HalfTime: max 1 weekends,  5-11 shifts, consec 3-5 free 3-5 busy
    All: unwanted [4][123], [3][12], complete weekends, single asst per day
    All: consec same shift days: Early 2-5, Day 2-28, Late 2-5, Night 4-5

    FullTime resources and the number of weekends they work in LOR are:
    
      NU_8 2, NU_9 1, CT_17 1, CT_18 0, CT_20 1, CT_25 1, TR_30 2, TR_32 3

    NB full-time can only work 20 shifts because of max 5 busy then
    min 2 free, e.g. 5-2-5-2-5-2-5-2 with 4*5 = 20 busy shifts.  But
    this as it stands is not viable because you work no weekends.  The
    opposite, 2-5-2-5-2-5-2-5 works 4 weekends which is no good either.
    Ideally you would want 5-2-5-4-5-2-5, which works 2 weekends, but
    the 4 free days are a defect.  More breaks is the only way to
    work 2 weekends, but that means a lower workload again.  This is
    why several of LOR's full-timers are working only 18 hours.  The
    conclusion is that trying to redistribute workload overloads is
    not going to help much.

    Resource types

    HeadNurse (HN_*) can also work as Nurse or Caretaker
    Nurse     (NU_*) can also work as Caretaker
    Caretaker (CT_*) works only as Caretaker
    Trainee   (TR_*) works only as Trainee

  "At least two days off after night shift" - if we recode this,
  we might do better on COI-BCDT-Sep.  But it's surprisingly hard.

  Option es_fresh_visits seems to be inconsistent, it causes
  things to become unvisited when there is an assumption that
  they are visited.  Needs looking into.  Currently commented
  out in khe_sr_combined.c.

  For the future:  time limit storing.  khe_sm_timer.c already
  has code for writing time limits, but not yet for reading.

  Work on time modelling paper for PATAT 2020.  The time model
  is an enabler for any projects I might do around ITC 2019,
  for example modelling student sectioning and implementing
  single student timetabling, so it is important for the future
  and needs to be got right.

  Time sets, time groups, resource sets, and resource groups
  ----------------------------------------------------------

    Thinking about whether I can remove construction of time
    neighbourhoods, by instead offering offset parameters on
    the time set operations (subset, etc.) which do the same.

    Need to use resource sets and time sets a lot more in the
    instance, for the constructed resource and time sets which
    in general have no name.  Maybe replace solution time groups
    and solution resource groups altogether.  But it's not
    trivial, because solution time groups are used by meets,
    and solution resource groups are used by tasks, both for
    handling domains (meet and task bounds).  What about

      typedef struct khe_time_set_rec {
          SSET elems;
      } KHE_TIME_SET;

    with SSET optimized by setting length to -1 to finalize.
    Most of the operations would have to be macros which
    add address-of operators in the style of SSET itself.

       KHE_TIME_SET KheTimeSetNeighbour(KHE_TIME_SET ts, int offset);

    would be doable with no memory allocation and one binary
    search (which could be optional for an internal version).

    I'm leaving this lie for now, something has to be done
    here but I'm not sure what, and there is no great hurry.

  There is a problem with preparing once and solving many times:
  adjustments for limit resources monitors depend on assignments
  in the vicinity, which may vary from one call to another.  The
  solution may well be simply to document the issue.

  At present resource matching is grouping then ungrouping during
  preparation, then grouping again when we start solving.  Can this
  be simplified?  There is a mark in the way.

  Document sset (which should really be khe_sset) and khe_set.

  I'm slightly worried that the comparison function for NRC
  worker constraints might have lost its transitivity now that
  history_after is being compared in some cases but not others.

  Look at the remaining special cases in all.map and see if some
  form of condensing can be applied to them.

  Might be a good idea to review the preserve_existing option in
  resource matching.  I don't exactly understand it at the moment.

  There seem to be several silly things in the current code that are
  about statistics.  I should think about collecting statistics in
  general, and implement something.  But not this time around.

  KheTaskFirstUnFixed is quite widely used, but I am beginning to
  are the same as mine (which GOAL's are not)?  If so I need
  to compare my results with theirs.  The paper is in the 2012
  PATAT proceedings, page 254.  Also it gives this site:

    https://www.kuleuven-kulak.be/nrpcompetition/competitor-ranking

  Can I find the results from the competition winner?  According to
  Santos et al. this was Valouxis et al, but their paper is in EJOR.

  Add code for limit resources monitors to khe_se_secondary.c.

  In KheClusterBusyTimesAugment, no use is being made of the
  allow_zero option at the moment.  Need to do this some time.

  Generalize the handling of the require_zero parameter of
  KheOverloadAugment, by allowing an ejection tree repair
  when the ejector depth is 1.  There is something like
  this already in KheClusterOverloadAugment, so look at
  that before doing anything else.

  There is an "Augment functions" section of the ejection chains
  chapter of the KHE guide that will need an update - do it last.

  (KHE) What about a general audit of how monitors report what
  is defective, with a view to finding a general rule for how
  to do this, and unifying all the monitors under that rule?
  The rule could be to store reported_deviation, renaming it
  to deviation, and to calculate a delta on that and have a
  function which applies the delta.  Have to look through all
  the monitors to see how that is likely to pan out.  But the
  general idea of a delta on the deviation does seem to be
  right, given that we want evaluation to be incremental.

  (KHE) For all monitors, should I include attached and unattached
  in the deviation function, so that attachment and unattachment
  are just like any other update functions?

  Ejection chains idea:  include main loop defect ejection trees
  in the major schedule, so that, at the end when main loop defects
  have resisted all previous attempts to repair them, we can try
  ejection trees on each in turn.  Make one change, produce several
  defects, and try to repair them all.  A good last resort?

  Ejection chains idea:  instead of requiring an ejection chain
  to improve the solution by at least (0, 1), require it to
  improve it by a larger amount, at first.  This will run much
  faster and will avoid trying to fix tiny problems until there
  is nothing better to do.  But have I already tried it?  It
  sounds a lot like es_limit_defects.
