@Chapter
    @Title { Archives and Solution Groups }
    @Tag { archives }
@Begin
@LP
This chapter describes the @C { KHE_ARCHIVE } and @C { KHE_SOLN_GROUP }
data types, representing archives and solution groups as in the XML
format.  Their use is optional, since instances are not required to lie
in archives, and solutions are not required to lie in solution groups.
@BeginSections

@Section
   @Title { Archives }
   @Tag { archives.archives }
@Begin
@LP
An archive is defined in the XML format to be a collection
of instances together with groups of solutions to those
instances.  There may be any number of instances and
solution groups.  To create a new, empty archive, call
@ID @C {
KHE_ARCHIVE KheArchiveMake(char *id, KHE_MODEL model, HA_ARENA_SET as);
}
Parameter @C { id } is an identifier for the archive.  It may be
@C { NULL }, but only if the archive is not going to be written.
Parameter @C { model } says what problem the archive models, for
which see just below.  Parameter @C { as } is the thread arena set
used for obtaining memory.  Appendix {@NumberOf ha.arena_sets}
introduces arena sets, and Appendix {@NumberOf impl.arena.plan} explains
why one arena set per thread is good.  You can also pass @C { NULL }
for @C { as }, but there will be some loss of efficiency in memory
allocation which could be critical when handling large archives.
@PP
Although created to support the XHSTT high school timetabling model,
KHE also supports an extended version of XHSTT, used for nurse
rostering.  Accordingly, type @C { KHE_MODEL } is
@ID @C {
typedef enum {
  KHE_MODEL_HIGH_SCHOOL_TIMETABLE,
  KHE_MODEL_EMPLOYEE_SCHEDULE
} KHE_MODEL;
}
The model affects the initial tag read by @C { KheArchiveRead } and written
by @C { KheArchiveWrite }, which is @C { <HighSchoolTimetableArchive> }
when it is @C { KHE_MODEL_HIGH_SCHOOL_TIMETABLE } and
@C { <EmployeeScheduleArchive> } when it is
@C { KHE_MODEL_EMPLOYEE_SCHEDULE }.  Instances
also have a model, which must agree with the model of any archive
they lie in.  Thus, it is not possible to mix instances with different
models in one archive.  Functions
@ID @C {
char *KheArchiveId(KHE_ARCHIVE archive);
KHE_MODEL KheArchiveModel(KHE_ARCHIVE archive);
}
return these attributes of an archive.  To set and retrieve the
back pointer (Section {@NumberOf intro.common}), call
@ID @C {
void KheArchiveSetBack(KHE_ARCHIVE archive, void *back);
void *KheArchiveBack(KHE_ARCHIVE archive);
}
Archive metadata may be set and retrieved by calling
@ID @C {
void KheArchiveSetMetaData(KHE_ARCHIVE archive, char *name,
  char *contributor, char *date, char *description, char *remarks);
void KheArchiveMetaData(KHE_ARCHIVE archive, char **name,
  char **contributor, char **date, char **description, char **remarks);
}
The values retrieved are copies of those passed in, as usual.
The initial values are all @C { NULL }.  When a metadata value is
required when writing an archive, any @C { NULL } or empty values
are written as @C { "No name" }, @C { "No contributor" }, etc.
There is also
@ID @C {
char *KheArchiveMetaDataText(KHE_ARCHIVE archive)
}
which returns a string containing the metadata as a
paragraph of English text, for example
@ID {0.90 1.0} @Scale @F {
This archive is XHSTT-2014, assembled by Gerhard Post on 2 March 2014.
}
The string lies in the archive's arena and is deleted when the
archive is deleted.
@PP
Initially an archive contains no instances and no solution groups.
Solution groups are added automatically as they are created, because
every solution group lies in exactly one archive.  An instance may
be added to an archive by calling
@ID @C {
bool KheArchiveAddInstance(KHE_ARCHIVE archive, KHE_INSTANCE ins);
}
@C { KheArchiveAddInstance } returns @C { true } if it succeeds in
adding @C { ins } to @C { archive }, and @C { false } otherwise,
which can either be because @C { archive } already contains an
instance with @C { ins }'s Id, or because the instance and archive
models differ.  The instance will appear after any instances
already present.  An instance may be deleted from an archive (but
not destroyed) by calling
@ID @C {
void KheArchiveDeleteInstance(KHE_ARCHIVE archive, KHE_INSTANCE ins);
}
@C { KheArchiveDeleteInstance } aborts if @C { ins } is not in
@C { archive }.  If there are any solutions for @C { ins } in
@C { archive }, they are deleted too.  The gap left by deleting the
instance is filled by shuffling subsequent instances up one place.
@PP
To visit the instances of an archive, call
@ID @C {
int KheArchiveInstanceCount(KHE_ARCHIVE archive);
KHE_INSTANCE KheArchiveInstance(KHE_ARCHIVE archive, int i);
}
The first returns the number of instances in @C { archive },
and the second returns the @C { i }'th of those instances,
counting from 0 as usual in C.  There is also
@ID @C {
bool KheArchiveRetrieveInstance(KHE_ARCHIVE archive, char *id,
  KHE_INSTANCE *ins, int *index);
}
If @C { archive } contains an instance with the given @C { id },
this function sets @C { ins } to that instance and @C { *index }
to its index in @C { archive } and returns @C { true }; otherwise
it sets @C { *ins } to @C { NULL } and @C { *index } to @C { -1 }
and returns @C { false }.  And
@ID @C {
bool KheArchiveContainsInstance(KHE_ARCHIVE archive,
  KHE_INSTANCE ins, int *index);
}
is the function to call when the instance is given and just its
index is needed.
@PP
For visiting the solution groups of an archive, call
@ID @C {
int KheArchiveSolnGroupCount(KHE_ARCHIVE archive);
KHE_SOLN_GROUP KheArchiveSolnGroup(KHE_ARCHIVE archive, int i);
}
similarly to visiting instances.  There is also
@ID @C {
bool KheArchiveRetrieveSolnGroup(KHE_ARCHIVE archive, char *id,
  KHE_SOLN_GROUP *soln_group);
}
which retrieves a solution group by @C { id }.
@End @Section

@Section
    @Title { Solution groups }
    @Tag { archives.soln_groups }
@Begin
@LP
A solution group is a set of solutions to instances of its
archive.  To create a solution group, call
@ID @C {
bool KheSolnGroupMake(KHE_ARCHIVE archive, char *id,
  KHE_SOLN_GROUP *soln_group);
}
Here @C { archive } is compulsory, and the solution group is added
to it.  Parameter @C { id } is the Id attribute from the XML file;
it is optional, with @C { NULL } meaning absent, although it is
compulsory if @C { archive } is to be written later.  If the operation
is successful, then @C { true } is returned with @C { *soln_group }
set to the new solution group; if not (which can only be because
@C { id } is already the Id of a solution group of @C { archive }),
then @C { false } is returned with @C { *soln_group } set to @C { NULL }.
@PP
To delete a solution group, including deleting it from its archive, call
@ID @C {
void KheSolnGroupDelete(KHE_SOLN_GROUP soln_group);
}
The solutions within @C { soln_group } are not deleted.
@PP
To set and retrieve the back pointer (Section {@NumberOf intro.common})
of a solution group, call
@ID @C {
void KheSolnGroupSetBack(KHE_SOLN_GROUP soln_group, void *back);
void *KheSolnGroupBack(KHE_SOLN_GROUP soln_group);
}
as usual.  To retrieve the archive and Id, call
@ID @C {
KHE_ARCHIVE KheSolnGroupArchive(KHE_SOLN_GROUP soln_group);
char *KheSolnGroupId(KHE_SOLN_GROUP soln_group);
}
Solution group metadata may be set and retrieved by calling
@ID @C {
void KheSolnGroupSetMetaData(KHE_SOLN_GROUP soln_group,
  char *contributor, char *date, char *description,
  char *publication, char *remarks);
void KheSolnGroupMetaData(KHE_SOLN_GROUP soln_group,
  char **contributor, char **date, char **description,
  char **publication, char **remarks);
}
As usual, copies of the strings are stored, not the originals.  As for
archive metadata, any of these strings may be @C { NULL } or empty.
KHE substitutes values @C { "No contributor" }, @C { "No date" },
etc. for such values when writing an archive, or omits them altogether
when XHSTT allows.  Also,
@ID @C {
char *KheSolnGroupMetaDataText(KHE_SOLN_GROUP soln_group);
}
returns a string containing the metadata as a paragraph of terse
English text.  The string lies in the solution group's arena and
will be deleted when the solution group is deleted.
@PP
Initially a solution group has no solutions.  These are added
and deleted by calling
@ID @C {
void KheSolnGroupAddSoln(KHE_SOLN_GROUP soln_group, KHE_SOLN soln);
void KheSolnGroupDeleteSoln(KHE_SOLN_GROUP soln_group, KHE_SOLN soln);
}
A solution can only be added when its instance lies in the solution
group's archive.
@PP
To visit the solutions of a solution group, call
@ID @C {
int KheSolnGroupSolnCount(KHE_SOLN_GROUP soln_group);
KHE_SOLN KheSolnGroupSoln(KHE_SOLN_GROUP soln_group, int i);
}
Solutions have no Ids, so there is no @C { KheSolnGroupRetrieveSoln }
function.  When solution @C { i } is deleted,
@C { KheSolnGroupSolnCount } decreases by 1, solution @C { i+1 }
becomes solution @C { i }, and so on.  To visit the solutions of
a solution group that solve a particular instance, call
@ID @C {
KHE_SOLN_SET KheSolnGroupInstanceSolnSet(KHE_SOLN_GROUP soln_group,
  KHE_INSTANCE ins);
}
Or if the index of the instance in the @C { soln_group }'s archive is
known, one can call
@ID @C {
KHE_SOLN_SET KheSolnGroupInstanceSolnSetByIndex(
  KHE_SOLN_GROUP soln_group, int index);
}
As described just below, @C { KHE_SOLN_SET } is a set of solutions.
The set returned by these functions holds the solutions in
@C { soln_group } for the indicated instance.  It is stored in
@C { soln_group } and must not be modified by the user, except
that it may be sorted.  KHE updates it as solutions are added and
deleted from its enclosing solution group, and deletes it when
its instance is deleted.
@End @Section

@Section
   @Title { Solution sets }
   @Tag { archives.soln.sets }
@Begin
@LP
Like a solution group, a solution set contains a set of solutions.
But, unlike a solution group, that is all it contains:  it is not
considered to lie in any archive, and it has no Id and no metadata.
@PP
To create a new, empty solution set, and to delete it (but not
its solutions), call
@ID @C {
KHE_SOLN_SET KheSolnSetMake(HA_ARENA a);
}
As usual it (but not its solutions) will be deleted when @C { a }
is deleted.  There is also
# void KheSolnSetDelete(KHE_SOLN_SET ss);
@ID @C {
void KheSolnSetClear(KHE_SOLN_SET ss);
}
which empties out @C { ss } without deleting it.  To add a
solution, and to delete one, call
@ID @C {
void KheSolnSetAddSoln(KHE_SOLN_SET ss, KHE_SOLN soln);
void KheSolnSetDeleteSoln(KHE_SOLN_SET ss, KHE_SOLN soln);
}
To find out if a solution set contains a given solution, call
@ID @C {
bool KheSolnSetContainsSoln(KHE_SOLN_SET ss, KHE_SOLN soln, int *pos);
}
It returns @C { true } if @C { ss } contains @C { soln }, setting
@C { *pos } to its index in @C { ss } if so.
@PP
To visit the elements of a solution set, call
@ID @C {
int KheSolnSetSolnCount(KHE_SOLN_SET ss);
KHE_SOLN KheSolnSetSoln(KHE_SOLN_SET ss, int i);
}
They have the order they were inserted in, unless this has
been changed by calling either of
@ID @C {
void KheSolnSetSort(KHE_SOLN_SET ss,
  int(*compar)(const void *, const void *));
void KheSolnSetSortUnique(KHE_SOLN_SET ss,
  int(*compar)(const void *, const void *));
}
@C { KheSolnSetSort } sorts the solutions according to
comparison function @C { compar }, which must be suitable
for passing to @C { qsort }.  @C { KheSolnSetSortUnique }
is the same, but afterwards it removes all but one of each
run of solutions for which @C { compar } returns 0.
@PP
One comparison function is already written, in one form
that makes sense to people and another that makes sense
to @C { qsort }:
@ID @C {
int KheIncreasingCostTypedCmp(KHE_SOLN soln1, KHE_SOLN soln2);
int KheIncreasingCostCmp(const void *t1, const void *t2);
}
It sorts the solution set so that the solutions have increasing
cost.  Solutions with equal cost have increasing running time.
Invalid solutions are treated as though they have infinite cost,
and solutions with no running time recorded are treated as though
they have infinite running time.
@PP
Finally,
@ID @C {
void KheSolnSetDebug(KHE_SOLN_SET ss, int verbosity,
  int indent, FILE *fp);
}
sends a debug print of @C { ss } to @C { fp } with the given verbosity
and indent.
@End @Section

#@Section
#   @Title { Write-only solutions }
#   @Tag { archives.writeonly }
#@Begin
#@LP
#As a space-saving measure, it is possible to add a solution
#to a solution group in a form which can be written by
#@C { KheArchiveWrite } (Section {@NumberOf archives.write})
#but which cannot be used for (and indeed, cannot be accessed
#for) any other purpose:
#@ID @C {
#void KheSolnGroupAddSolnWriteOnly(KHE_SOLN_GROUP soln_group,
#  KHE_SOLN soln);
#}
#This stores @C { soln } in @C { soln_group } in a minimalist
#format.  Functions @C { KheSolnGroupSolnCount } and
#@C { KheSolnGroupSoln } (Section {@NumberOf archives.soln_groups})
#are unaware of the existence of solutions added in this way; their
#only effect is on @C { KheArchiveWrite }.
#@PP
#Although @C { soln } is unchanged by @C { KheSolnGroupAddSolnWriteOnly },
#it would be usual to delete it afterwards, like this:
#@ID @C {
#KheSolnGroupAddSolnWriteOnly(soln_group, soln);
#KheSolnDelete(soln);
#}
#In this way the memory consumed by @C { soln } is made available
#for holding other solutions, yet its equivalent can still be
#written out by @C { KheArchiveWrite } later.
#@End @Section

@Section
   @Title { Reading archives }
   @Tag { archives.read }
@Begin
@LP
KHE reads and writes archives in XHSTT, a standard XML format
@Cite { $post2008 }, and in XESTT, an extension of XHSTT for employee
scheduling problems @Cite { $kingston2018history, $kingston2018xestt }.
To read an archive, call
@ID @C {
bool KheArchiveRead(FILE *fp, HA_ARENA_SET as, KHE_ARCHIVE *archive,
  KML_ERROR *ke, bool audit_and_fix, bool resource_type_partitions,
  bool infer_resource_partitions, bool limit_busy_recode,
  bool allow_invalid_solns, KHE_SOLN_TYPE soln_type, FILE *echo_fp);
}
File @C { fp } must be open for reading UTF-8, and it remains open after
the call returns.  If, starting from its current position, @C { fp }
contains a legal XML archive, then @C { KheArchiveRead } sets @C { *archive }
to that archive, passing it @C { as } as its arena set parameter, and
@C { *ke } to @C { NULL } and returns @C { true } with @C { fp } moved
to the first character after the archive.  If there was a problem
reading the file, then it sets @C { *archive } to @C { NULL } and
@C { *ke } to an error object and returns @C { false }.  Any reports
in the archive are discarded without checking.
@PP
Type @C { KML_ERROR } is from the KML module packaged with KHE.  A
full description of the KML module appears in Section {@NumberOf kml}.
Given an object of type @C { KML_ERROR }, operations
@ID @C {
int KmlErrorLineNum(KML_ERROR ke);
int KmlErrorColNum(KML_ERROR ke);
char *KmlErrorString(KML_ERROR ke);
}
return the line number, the column number, and a string description
of the error.
@PP
@C { KheArchiveRead } builds the archive using the functions of
this guide; there is nothing special about the archive it builds.
The model, for the archive and instances, depends on the initial tag:
@C { KHE_MODEL_HIGH_SCHOOL_TIMETABLE } when it is
@C { <HighSchoolTimetableArchive> }, and
@C { KHE_MODEL_EMPLOYEE_SCHEDULE } when it is
@C { <EmployeeScheduleArchive> }.
@PP
The @C { audit_and_fix }, @C { resource_type_partitions },
@C { infer_resource_partitions }, and @C { limit_busy_recode }
parameters are passed
on to @C { KheInstanceMakeEnd } (Section {@NumberOf instances_creation}).
# , and @C { with_monitors } is passed on to @C { KheSolnMake }
# (Section {@NumberOf solutions.objects}).
@C { KheArchiveRead } builds complete representations of the solutions
it reads.  To be precise, it calls functions @C { KheSolnMakeCompleteRepresentation },
@C { KheSolnAssignPreassignedTimes }, and
@C { KheSolnAssignPreassignedResources }
(Section {@NumberOf solutions.complete}), but not
@C { KheSolnMatchingBegin } or @C { KheSolnEvennessBegin }
(Chapter {@NumberOf matchings}).
@PP
Usually, if there are errors in the file, @C { KheArchiveRead } returns
@C { false } and sets @C { *ke } to the first error.  But if
@C { allow_invalid_solns } is @C { true }, then some errors lying in
solutions are handled differently:  the erroneous solutions are converted
to invalid placeholders (Section {@NumberOf solutions.top.placeholder}).
Each invalid placeholder solution contains its first error, and none of
its errors cause @C { false } to be returned or @C { *ke } to be set.
Not all errors, not even all errors lying in solutions, can be handled
in this way; those that cannot cause @C { KheArchiveRead } to return
@C { false } and set @C { *ke } as usual.
@PP
Each valid solution is passed to function @C { KheSolnTypeReduce }
along with parameter @C { soln_type }.  If @C { soln_type } is
@C { KHE_SOLN_ORDINARY } this does nothing, but other values
reduce the solution to a placeholder, freeing up a lot of memory
which is re-used for reading other solutions.  The value of
@C { soln_type } may not be @C { KHE_SOLN_INVALID_PLACEHOLDER }.
See Section {@NumberOf solutions.top.placeholder} for
@C { KheSolnTypeReduce } and the other choices for @C { soln_type }.
@PP
@C { KheArchiveRead } calls @C { KmlReadFile } (Section {@NumberOf kml.read}),
passing @C { echo_fp } to it.  The characters read are echoed to
@C { echo_fp } if it is non-@C { NULL }; it would normally be @C { NULL }.
@End @Section

@Section
   @Title { Reading archives incrementally }
   @Tag { archives.inc }
@Begin
@LP
A large archive may have to be read one solution at a time.  For
this, call
@ID {0.95 1.0} @Scale @C {
bool KheArchiveReadIncremental(FILE *fp, HA_ARENA_SET as,
  KHE_ARCHIVE *archive, KML_ERROR *ke, bool audit_and_fix,
  bool resource_type_partitions, bool infer_resource_partitions,
  bool limit_busy_recode, bool allow_invalid_solns,
  KHE_SOLN_TYPE soln_type, FILE *echo_fp,
  KHE_ARCHIVE_FN archive_begin_fn, KHE_ARCHIVE_FN archive_end_fn,
  KHE_SOLN_GROUP_FN soln_group_begin_fn,
  KHE_SOLN_GROUP_FN soln_group_end_fn, KHE_SOLN_FN soln_fn, void *impl);
}
The return value and the parameters up to @C { echo_fp } inclusive are
as for @C { KheArchiveRead }.  The remaining parameters are callback
functions, except the last, @C { impl }, which is not used by KHE but
is instead passed through to the calls on the callback functions.  Any
or all of the callback functions may be @C { NULL }, in which case the
corresponding callbacks are not made.
@PP
Callback function @C { archive_begin_fn } is called by
@C { KheArchiveReadIncremental } at the start of the archive.
It must be written by the user like this:
@ID @C {
void archive_begin_fn(KHE_ARCHIVE archive, void *impl)
{
  ...
}
}
Its @C { archive } parameter is set to the archive that
@C { KheArchiveReadIncremental } will eventually build, the one it
returns in its @C { *archive } parameter; its @C { impl } parameter
contains the value of the @C { impl } parameter of
@C { KheArchiveReadIncremental }.  At the time of this call,
@C { archive } contains its Id, metadata, and model attributes,
but no instances and no solution groups.
@PP
Callback function @C { archive_end_fn } is called at the end of the
archive, just before @C { KheArchiveReadIncremental } itself returns:
@ID @C {
void archive_end_fn(KHE_ARCHIVE archive, void *impl)
{
  ...
}
}
When this function is called, @C { archive } contains all of its
instances and solution groups.  If @C { KheArchiveReadIncremental }
returns @C { true }, there has been one callback to @C { archive_begin_fn }
and one to @C { archive_end_fn }, if non-@C { NULL }.
@PP
Callback function @C { soln_group_begin_fn } is called at the start
of each solution group:
@ID @C {
void soln_group_begin_fn(KHE_SOLN_GROUP soln_group, void *impl)
{
  ...
}
}
Its @C { soln_group } parameter is set to one of the solution
groups that the final archive will eventually contain, and
its @C { impl } parameter is as before.  At the time of this
call, @C { soln_group } contains its Id and MetaData, and
@C { KheSolnGroupArchive(soln_group) } returns the enclosing
archive, but there are no solutions in @C { soln_group }.
@PP
Callback function @C { soln_group_end_fn } is called at the end
of each solution group:
@ID @C {
void soln_group_end_fn(KHE_SOLN_GROUP soln_group, void *impl)
{
  ...
}
}
At the time of this call, @C { soln_group } contains all its
solutions.
@PP
Finally, callback function @C { soln_fn } is called after
each solution is read:
@ID @C {
void soln_fn(KHE_SOLN soln, void *impl)
{
  ...
}
}
The solution is complete, and @C { KheSolnSolnGroup(soln) } returns
the enclosing solution group.
@PP
The purpose of incremental reading is to process the solutions
as they are read, so that they can be deleted and their memory
reclaimed.  For example, to replace each solution by a placeholder,
pass @C { NULL } for all callbacks except @C { soln_fn }, which
would be defined like this:
@ID @C {
void soln_fn(KHE_SOLN soln, void *impl)
{
  if( KheSolnType(soln) == KHE_SOLN_ORDINARY )
    KheSolnReduceToPlaceholder(soln, false);
}
}
The test is needed only if @C { allow_invalid_solns } is @C { true }.
@C { KheSolnReduceToPlaceholder }
(Section {@NumberOf solutions.top.placeholder}) reclaims most of the
memory of @C { soln }, leaving just the @C { soln } object itself and
a few attributes, including its cost.  In this way, the total memory
cost is reduced to not much more than the memory needed to hold the
instances, but enough information is retained to support operations
which (for example) print tables of solutions and their costs.  Of
course, @C { KheArchiveRead } has the @C { soln_type } parameter
which can be used to instruct it to do these reductions anyway.
@PP
Other applications might process @C { soln } in some way
(print timetables, for example) before finishing with a call to
@C { KheSolnReduceToPlaceholder }, or even @C { KheSolnDelete }.
@End @Section

@Section
   @Title { Reading archives from the command line }
   @Tag { archives.command }
@Begin
@LP
Reading an archive from the command line basically means opening the
file named by a command-line argument and calling @C { KheArchiveRead }.
Beyond that, there may be a need to process the archive before using
it, for example to remove its solution groups.  Function
@ID @C {
KHE_ARCHIVE KheArchiveReadFromCommandLine(int argc, char *argv[],
  int *pos, HA_ARENA_SET as, bool audit_and_fix,
  bool resource_type_partitions, bool infer_resource_partitions,
  bool limit_busy_recode, bool allow_invalid_solns,
  KHE_SOLN_TYPE soln_type, FILE *echo_fp);
}
offers a standard way to do that.  Here @C { argc } and @C { argv }
are exactly as they were passed to the main program, and @C { *pos }
is an index into @C { argv }, to a point where the name of an archive
is expected.
@PP
@C { KheArchiveReadFromCommandLine } first opens the file whose name
is @C { argv[*pos] }, calls @C { KheArchiveRead }, and increments
@C { *pos } to inform the caller that the argument at @C { *pos }
has been processed.  The name may be @F { - }, meaning standard
input.  Then, while command-line arguments beginning with @F { -x },
@F { -i }, @F { -X }, and @F { -I } follow the name, it modifies
the in-memory version of the archive as instructed by those
arguments.  Finally, it returns the archive, with @C { *pos }
moved to the index of the first unprocessed argument, or to
@C { argc } if the argument list becomes exhausted.
@PP
The @F { -x }, @F { -i }, @F { -X }, and @F { -I } arguments have
this syntax and meaning:
@TaggedList

@DTI { @F "-x<id>{,<id>}" } {
Delete instances (and their solutions) with the given Ids.
}

@DTI { @F "-i<id>{,<id>}" } {
Include only instances (and their solutions) with the given Ids;
delete all other instances.
}

@DTI { @F "-X<id>{,<id>}" } {
Delete solution groups with the given Ids.
}

@DTI { @F "-I<id>{,<id>}" } {
Include only solution groups with the given Ids; delete all
other solution groups.
}

@EndList
As a special case, @F { -X } with no ids means to delete all solution
groups.
@PP
Arguments @F { -x } and @F { -i } may not be used together, and @F { -X }
and @F { -I } may not be used together.  If there is a problem,
@C { KheArchiveReadFromCommandLine } prints a message and calls @C { exit(1) }.
@PP
At present there is no @C { KheArchiveReadFromCommandLineIncremental }
function combining @C { KheArchiveReadFromCommandLine } with
@C { KheArchiveReadIncremental }.
@End @Section

@Section
   @Title { Writing archives and solution groups }
   @Tag { archives.write }
@Begin
@LP
To write an archive to a file, call
@ID @C {
void KheArchiveWrite(KHE_ARCHIVE archive, bool with_reports, FILE *fp);
}
File @C { fp } must be open for writing UTF-8 characters, and it
remains open after the call returns.  If @C { with_reports } is
@C { true }, each written solution contains a @C { Report }
section evaluating the solution.
@PP
If the archive's model is @C { KHE_MODEL_HIGH_SCHOOL_TIMETABLE }, the
initial tag written to @C { fp } will be @C { <HighSchoolTimetableArchive> }.
If the model is @C { KHE_MODEL_EMPLOYEE_SCHEDULE }, the initial tag will be
@C { <EmployeeScheduleArchive> }.
@PP
Ids and names are optional in KHE but compulsory when writing XML:
if any are missing, @C { KheArchiveWrite } writes an incomplete
file and aborts with an error message.  They will all be present
when @C { archive } was produced by @C { KheArchiveRead }.
@PP
If any of @C { archive }'s solutions are invalid or unwritable
placeholders, @C { KheArchiveWrite } aborts.  If @C { with_reports }
is @C { true }, any placeholder solution at all causes an abort.
@PP
When an event has a preassigned time, there is a problem if one of
its meets is not assigned that time.  If the meet is assigned some
other time (which is possible in KHE, although not easy), then
writing that time will cause the solution to be declared invalid
when it is re-read.  If the meet is not assigned any time, then,
whether or not the preassigned time is written, the meaning is that
the preassigned time is assigned, which is not the true state of
the solution.  The same problem arises with preassigned event
resources whose tasks are not assigned the preassigned resource.
@PP
Accordingly, @C { KheArchiveWrite } also writes an incomplete
file and aborts with an error message when it encounters a meet
(or task) derived from a preassigned event (or event resource)
whose assigned time (or resource) is unequal to the preassigned
time (or resource).
@PP
When writing solutions, @C { KheArchiveWrite } writes as little as
possible.  It does not write an unassigned or preassigned task.
It does not write a meet if its duration equals the duration of
the corresponding event, its time is unassigned or preassigned,
and its tasks are not written according to the rule just given
(see also Section {@NumberOf solutions.complete}).
@PP
Two similar functions are
@ID @C {
void KheArchiveWriteSolnGroup(KHE_ARCHIVE archive,
  KHE_SOLN_GROUP soln_group, bool with_reports, FILE *fp);
void KheArchiveWriteWithoutSolnGroups(KHE_ARCHIVE archive, FILE *fp);
}
They also write @C { archive }, omitting all its solution groups, or
all of them except @C { soln_group }.  They have been superseded,
in practice, by @C { KheArchiveReadFromCommandLine }
(Section {@NumberOf archives.command}).
@End @Section

@EndSections
@End @Chapter
