pspp-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Pspp-cvs] pspp Smake src/data/automake.mk src/data/case-t... [simpler-p


From: Ben Pfaff
Subject: [Pspp-cvs] pspp Smake src/data/automake.mk src/data/case-t... [simpler-proc]
Date: Sat, 14 Apr 2007 05:04:24 +0000

CVSROOT:        /cvsroot/pspp
Module name:    pspp
Branch:         simpler-proc
Changes by:     Ben Pfaff <blp> 07/04/14 05:04:23

Modified files:
        .              : Smake 
        src/data       : automake.mk case-tmpfile.c case-tmpfile.h 
                         case.c case.h casereader.c casereader.h 
                         casewindow.c casewriter.c casewriter.h 
                         datasheet.c datasheet.h 
        src/language   : command.def 
        src/language/tests: automake.mk 
        src/libpspp    : array.c array.h automake.mk range-set.c 
                         range-set.h tower.c tower.h 
        src/ui/gui     : automake.mk psppire-case-file.c 
                         psppire-data-store.c psppire.c 
Added files:
        src/data       : sparse-cases.c sparse-cases.h 
        src/language/tests: check-model.h check-model.q datasheet-test.c 
        src/libpspp    : model-checker.c model-checker.h 

Log message:
        Make significant progress:
        
                * Finish up and test the datasheet implementation.
        
                * As part of that, implement a general-purpose model checker
                  that should be useful for testing other code.
        
                * Enable compiling the GUI.

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/pspp/Smake?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.49.2.2&r2=1.49.2.3
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/automake.mk?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.15.2.1&r2=1.15.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/case-tmpfile.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/case-tmpfile.h?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/case.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.10.2.1&r2=1.10.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/case.h?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.9.2.1&r2=1.9.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/casereader.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/casereader.h?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/casewindow.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.2&r2=1.1.2.3
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/casewriter.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/casewriter.h?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/datasheet.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.3&r2=1.1.2.4
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/datasheet.h?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/sparse-cases.c?cvsroot=pspp&only_with_tag=simpler-proc&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/pspp/src/data/sparse-cases.h?cvsroot=pspp&only_with_tag=simpler-proc&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/pspp/src/language/command.def?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.14.2.1&r2=1.14.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/language/tests/automake.mk?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.4.2.1&r2=1.4.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/language/tests/check-model.h?cvsroot=pspp&only_with_tag=simpler-proc&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/pspp/src/language/tests/check-model.q?cvsroot=pspp&only_with_tag=simpler-proc&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/pspp/src/language/tests/datasheet-test.c?cvsroot=pspp&only_with_tag=simpler-proc&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/array.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.10.2.1&r2=1.10.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/array.h?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.6&r2=1.6.2.1
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/automake.mk?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.22.2.7&r2=1.22.2.8
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/range-set.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.2&r2=1.1.2.3
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/range-set.h?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.3&r2=1.1.2.4
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/tower.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/tower.h?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.1.2.1&r2=1.1.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/model-checker.c?cvsroot=pspp&only_with_tag=simpler-proc&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/pspp/src/libpspp/model-checker.h?cvsroot=pspp&only_with_tag=simpler-proc&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/pspp/src/ui/gui/automake.mk?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.21.2.2&r2=1.21.2.3
http://cvs.savannah.gnu.org/viewcvs/pspp/src/ui/gui/psppire-case-file.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.17.2.1&r2=1.17.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/ui/gui/psppire-data-store.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.33.2.1&r2=1.33.2.2
http://cvs.savannah.gnu.org/viewcvs/pspp/src/ui/gui/psppire.c?cvsroot=pspp&only_with_tag=simpler-proc&r1=1.35.2.1&r2=1.35.2.2

Patches:
Index: Smake
===================================================================
RCS file: /cvsroot/pspp/pspp/Smake,v
retrieving revision 1.49.2.2
retrieving revision 1.49.2.3
diff -u -b -r1.49.2.2 -r1.49.2.3
--- Smake       20 Mar 2007 00:08:49 -0000      1.49.2.2
+++ Smake       14 Apr 2007 05:04:23 -0000      1.49.2.3
@@ -11,15 +11,18 @@
        byteswap \
        c-ctype \
        c-strtod \
+       crypto/md4 \
        dirname \
        exit \
        full-read \
        full-write \
+       fwriteerror \
        gethostname \
        getline \
        getlogin_r \
        getopt \
        gettext-h \
+       gettimeofday \
        intprops \
        inttostr \
        linebreak \

Index: src/data/automake.mk
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/automake.mk,v
retrieving revision 1.15.2.1
retrieving revision 1.15.2.2
diff -u -b -r1.15.2.1 -r1.15.2.2
--- src/data/automake.mk        19 Mar 2007 21:36:24 -0000      1.15.2.1
+++ src/data/automake.mk        14 Apr 2007 05:04:23 -0000      1.15.2.2
@@ -65,6 +65,8 @@
        src/data/scratch-writer.h \
        src/data/settings.c \
        src/data/settings.h \
+       src/data/sparse-cases.c \
+       src/data/sparse-cases.h \
        src/data/sys-file-private.c \
        src/data/sys-file-private.h \
        src/data/sys-file-reader.c \

Index: src/data/case-tmpfile.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/case-tmpfile.c,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/data/case-tmpfile.c     19 Mar 2007 21:36:24 -0000      1.1.2.1
+++ src/data/case-tmpfile.c     14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -79,8 +79,9 @@
 #endif
 
 static void
-mark_error (struct case_tmpfile *ctf) 
+mark_error (const struct case_tmpfile *ctf_) 
 {
+  struct case_tmpfile *ctf = (struct case_tmpfile *) ctf_;
   if (ctf->file != NULL) 
     {
       fclose (ctf->file);
@@ -89,12 +90,12 @@
 }
 
 static bool
-seek_to_case (struct case_tmpfile *ctf, casenumber case_idx) 
+do_seek (const struct case_tmpfile *ctf,
+         casenumber case_idx, size_t value_idx) 
 {
-  if (ctf->file != NULL
-      && fseeko (ctf->file,
-                 (off_t) sizeof (union value) * ctf->value_cnt * case_idx,
-                 SEEK_SET) == 0)
+  off_t value_ofs = value_idx + (off_t) ctf->value_cnt * case_idx;
+  off_t byte_ofs = sizeof (union value) * value_ofs;
+  if (ctf->file != NULL && fseeko (ctf->file, byte_ofs, SEEK_SET) == 0)
     return true;
 
   if (!case_tmpfile_error (ctf)) 
@@ -106,17 +107,17 @@
 }
 
 bool
-case_tmpfile_get (const struct case_tmpfile *ctf_, casenumber index,
-                  struct ccase *c) 
+case_tmpfile_get_values (const struct case_tmpfile *ctf,
+                         casenumber case_idx, size_t start_value,
+                         union value values[], size_t value_cnt) 
 {
-  struct case_tmpfile *ctf = (struct case_tmpfile *) ctf_;
-  if (seek_to_case (ctf, index)) 
-    {
-      case_create (c, ctf->value_cnt);
-      if (fread (case_data_all_rw (c), sizeof (union value) * ctf->value_cnt,
-                 1, ctf->file) != 1) 
+  assert (value_cnt < ctf->value_cnt);
+  assert (value_cnt + start_value <= ctf->value_cnt);
+
+  if (!do_seek (ctf, case_idx, start_value))
+    return false;
+  if (fread (values, sizeof (union value) * value_cnt, 1, ctf->file) != 1) 
         {
-          case_destroy (c);
           mark_error (ctf);
           if (ferror (ctf->file))
             error (0, errno, _("reading temporary file"));
@@ -124,29 +125,51 @@
             error (0, 0, _("unexpected end of file reading temporary file"));
           else
             NOT_REACHED ();
+      return false;
         } 
-    }
+  return true;
+}
 
-  if (!case_tmpfile_error (ctf))
+bool
+case_tmpfile_get_case (const struct case_tmpfile *ctf, casenumber case_idx,
+                       struct ccase *c) 
+{
+  case_create (c, ctf->value_cnt);
+  if (case_tmpfile_get_values (ctf, case_idx, 0,
+                               case_data_all_rw (c), ctf->value_cnt))
     return true;
   else
     {
+      case_destroy (c);
       case_nullify (c);
       return false;
     }
 }
 
 bool
-case_tmpfile_put (struct case_tmpfile *ctf, casenumber index,
-                  struct ccase *c) 
+case_tmpfile_put_values (struct case_tmpfile *ctf,
+                         casenumber case_idx, size_t start_value,
+                         const union value values[], size_t value_cnt)
+
 {
-  if (seek_to_case (ctf, index)
-      && fwrite (case_data_all (c), sizeof (union value) * ctf->value_cnt,
-                 1, ctf->file) != 1) 
+  if (!do_seek (ctf, case_idx, start_value))
+    return false;
+  if (fwrite (values, sizeof (union value) * value_cnt, 1, ctf->file) != 1)
     {
       mark_error (ctf); 
       error (0, errno, _("writing to temporary file"));
+      return false;
     }
+  return true;
+}                         
+
+bool
+case_tmpfile_put_case (struct case_tmpfile *ctf, casenumber case_idx,
+                       struct ccase *c) 
+{
+  bool ok = case_tmpfile_put_values (ctf, case_idx, 0,
+                                     case_data_all (c), ctf->value_cnt);
   case_destroy (c);
-  return !case_tmpfile_error (ctf);
+  return ok;
 }
+

Index: src/data/case-tmpfile.h
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/case-tmpfile.h,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/data/case-tmpfile.h     19 Mar 2007 21:36:24 -0000      1.1.2.1
+++ src/data/case-tmpfile.h     14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -25,9 +25,16 @@
 bool case_tmpfile_destroy (struct case_tmpfile *);
 bool case_tmpfile_error (const struct case_tmpfile *);
 
-bool case_tmpfile_get (const struct case_tmpfile *, casenumber index,
-                       struct ccase *);
-bool case_tmpfile_put (struct case_tmpfile *, casenumber index,
-                       struct ccase *);
+bool case_tmpfile_get_values (const struct case_tmpfile *,
+                              casenumber, size_t start_value,
+                              union value[], size_t value_cnt);
+bool case_tmpfile_get_case (const struct case_tmpfile *,
+                            casenumber, struct ccase *);
+
+bool case_tmpfile_put_values (struct case_tmpfile *,
+                              casenumber, size_t start_value,
+                              const union value[], size_t value_cnt);
+bool case_tmpfile_put_case (struct case_tmpfile *,
+                            casenumber, struct ccase *);
 
 #endif /* data/case-tmpfile.h */

Index: src/data/case.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/case.c,v
retrieving revision 1.10.2.1
retrieving revision 1.10.2.2
diff -u -b -r1.10.2.1 -r1.10.2.2
--- src/data/case.c     19 Mar 2007 21:36:24 -0000      1.10.2.1
+++ src/data/case.c     14 Apr 2007 05:04:23 -0000      1.10.2.2
@@ -95,9 +95,9 @@
   if (clone != orig) 
     *clone = *orig;
   orig->case_data->ref_cnt++;
-//#ifdef DEBUGGING
+#ifdef DEBUGGING
   case_unshare (clone);
-//#endif
+#endif
 }
 
 /* Replaces DST by SRC and nullifies SRC.
@@ -210,35 +210,32 @@
     }
 }
 
-/* Copies case C to OUTPUT.
-   OUTPUT_SIZE is the number of `union values' in OUTPUT,
-   which must match the number of `union values' in C. */
+/* Copies VALUE_CNT values out of case C to VALUES, starting at
+   the given START_IDX. */
 void
-case_to_values (const struct ccase *c, union value *output,
-                size_t output_size UNUSED) 
+case_copy_out (const struct ccase *c,
+               size_t start_idx, union value *values, size_t value_cnt) 
 {
   assert (c->case_data->ref_cnt > 0);
-  assert (output_size == c->case_data->value_cnt);
-  assert (output != NULL || output_size == 0);
+  assert (value_cnt <= c->case_data->value_cnt);
+  assert (start_idx + value_cnt <= c->case_data->value_cnt);
 
-  memcpy (output, c->case_data->values,
-          c->case_data->value_cnt * sizeof *output);
+  memcpy (values, c->case_data->values + start_idx,
+          value_cnt * sizeof *values);
 }
 
-/* Copies INPUT into case C.
-   INPUT_SIZE is the number of `union values' in INPUT,
-   which must match the number of `union values' in C. */
+/* Copies VALUE_CNT values from VALUES into case C, staring at
+   the given START_IDX. */
 void
-case_from_values (struct ccase *c, const union value *input,
-                  size_t input_size UNUSED) 
+case_copy_in (struct ccase *c,
+              size_t start_idx, const union value *values, size_t value_cnt) 
 {
   assert (c->case_data->ref_cnt > 0);
-  assert (input_size == c->case_data->value_cnt);
-  assert (input != NULL || input_size == 0);
+  assert (value_cnt <= c->case_data->value_cnt);
+  assert (start_idx + value_cnt <= c->case_data->value_cnt);
 
-  case_unshare (c);
-  memcpy (c->case_data->values, input,
-          c->case_data->value_cnt * sizeof *input);
+  memcpy (c->case_data->values + start_idx, values,
+          value_cnt * sizeof *values);
 }
 
 /* Returns a pointer to the `union value' used for the

Index: src/data/case.h
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/case.h,v
retrieving revision 1.9.2.1
retrieving revision 1.9.2.2
diff -u -b -r1.9.2.1 -r1.9.2.2
--- src/data/case.h     19 Mar 2007 21:36:24 -0000      1.9.2.1
+++ src/data/case.h     14 Apr 2007 05:04:23 -0000      1.9.2.2
@@ -60,8 +60,10 @@
                             const struct ccase *src, size_t src_idx,
                             size_t cnt);
 
-void case_to_values (const struct ccase *, union value *, size_t);
-void case_from_values (struct ccase *, const union value *, size_t);
+void case_copy_out (const struct ccase *,
+                       size_t start_idx, union value *, size_t value_cnt);
+void case_copy_in (struct ccase *,
+                   size_t start_idx, const union value *, size_t value_cnt);
 
 const union value *case_data (const struct ccase *, const struct variable *);
 double case_num (const struct ccase *, const struct variable *);

Index: src/data/casereader.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/casereader.c,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/data/casereader.c       19 Mar 2007 21:36:24 -0000      1.1.2.1
+++ src/data/casereader.c       14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -166,6 +166,24 @@
   return reader->case_cnt;
 }
 
+casenumber
+casereader_count_cases (struct casereader *reader)
+{
+  casenumber case_cnt = casereader_get_case_cnt (reader);
+  if (case_cnt == CASENUMBER_INVALID) 
+    {
+      struct casereader *clone;
+      struct ccase c;
+
+      case_cnt = 0;
+      clone = casereader_clone (reader);
+      for (; casereader_read (clone, &c); case_destroy (&c)) 
+        case_cnt++;
+      casereader_destroy (clone);
+    }
+  return case_cnt;
+}
+
 size_t
 casereader_get_value_cnt (struct casereader *reader) 
 {

Index: src/data/casereader.h
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/casereader.h,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/data/casereader.h       19 Mar 2007 21:36:24 -0000      1.1.2.1
+++ src/data/casereader.h       14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -38,6 +38,7 @@
 bool casereader_error (const struct casereader *);
 
 casenumber casereader_get_case_cnt (struct casereader *);
+casenumber casereader_count_cases (struct casereader *);
 size_t casereader_get_value_cnt (struct casereader *);
 
 struct casereader *

Index: src/data/casewindow.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/casewindow.c,v
retrieving revision 1.1.2.2
retrieving revision 1.1.2.3
diff -u -b -r1.1.2.2 -r1.1.2.3
--- src/data/casewindow.c       28 Mar 2007 17:26:48 -0000      1.1.2.2
+++ src/data/casewindow.c       14 Apr 2007 05:04:23 -0000      1.1.2.3
@@ -263,7 +263,7 @@
 casewindow_file_push_head (void *cwf_, struct ccase *c) 
 {
   struct casewindow_file *cwf = cwf_;
-  if (case_tmpfile_put (cwf->file, cwf->head, c)) 
+  if (case_tmpfile_put_case (cwf->file, cwf->head, c)) 
     {
       cwf->head++;
       return true;
@@ -287,7 +287,7 @@
 casewindow_file_get_case (void *cwf_, casenumber ofs, struct ccase *c) 
 {
   struct casewindow_file *cwf = cwf_;
-  return case_tmpfile_get (cwf->file, cwf->tail + ofs, c);
+  return case_tmpfile_get_case (cwf->file, cwf->tail + ofs, c);
 }
 
 static casenumber

Index: src/data/casewriter.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/casewriter.c,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/data/casewriter.c       19 Mar 2007 21:36:24 -0000      1.1.2.1
+++ src/data/casewriter.c       14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -89,20 +89,10 @@
 struct casereader *
 casewriter_make_reader (struct casewriter *writer)
 {
-  return writer->class->convert_to_reader (writer, writer->private);
-}
-
-void
-casewriter_transfer (struct casewriter *dst, struct casewriter *src) 
-{
-  struct casereader *src_reader;
-  struct ccase c;
-
-  /* FIXME: error propagation? */
-  src_reader = casewriter_make_reader (src);
-  while (casereader_read (src_reader, &c))
-    casewriter_write (dst, &c);
-  casewriter_destroy (src);
+  struct casereader *reader;
+  reader = writer->class->convert_to_reader (writer, writer->private);
+  free (writer);
+  return reader;
 }
 
 static const struct casewriter_class casewriter_window_class;

Index: src/data/casewriter.h
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/casewriter.h,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/data/casewriter.h       19 Mar 2007 21:36:24 -0000      1.1.2.1
+++ src/data/casewriter.h       14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -36,7 +36,6 @@
 
 bool casewriter_error (const struct casewriter *);
 struct casereader *casewriter_make_reader (struct casewriter *);
-void casewriter_transfer (struct casewriter *, struct casewriter *);
 
 struct casewriter *
 casewriter_create_translator (struct casewriter *, 

Index: src/data/datasheet.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/datasheet.c,v
retrieving revision 1.1.2.3
retrieving revision 1.1.2.4
diff -u -b -r1.1.2.3 -r1.1.2.4
--- src/data/datasheet.c        1 Apr 2007 19:32:15 -0000       1.1.2.3
+++ src/data/datasheet.c        14 Apr 2007 05:04:23 -0000      1.1.2.4
@@ -21,17 +21,21 @@
 #include <data/datasheet.h>
 
 #include <stdlib.h>
+#include <string.h>
 
 #include <data/buffered-reader.h>
 #include <data/casereader.h>
-#include <data/case-tmpfile.h>
-#include <data/settings.h>
+#include <data/casewriter.h>
+#include <data/sparse-cases.h>
+#include <libpspp/array.h>
 #include <libpspp/assertion.h>
+#include <libpspp/model-checker.h>
 #include <libpspp/range-map.h>
 #include <libpspp/range-set.h>
-#include <libpspp/sparse-array.h>
 #include <libpspp/tower.h>
 
+#include "minmax.h"
+#include "md4.h"
 #include "xalloc.h"
 
 static struct axis *axis_create (void);
@@ -40,7 +44,11 @@
 static bool axis_allocate (struct axis *, unsigned long int request,
                            unsigned long int *start,
                            unsigned long int *width);
-static void axis_extend (struct axis *, unsigned long int width);
+static void axis_make_available (struct axis *,
+                                unsigned long int start,
+                                unsigned long int width);
+static void axis_extend (struct axis *, unsigned long int width,
+                         unsigned long int *start);
 
 static unsigned long int axis_map (const struct axis *, unsigned long log_pos);
 
@@ -56,26 +64,23 @@
                        unsigned long int new_start,
                        unsigned long int cnt);
 
-enum rw_op 
-  {
-    OP_READ,
-    OP_WRITE
-  };
-
 static struct source *source_create_empty (size_t column_cnt);
-static struct source *source_create_from_casereader (struct casereader *,
-                                                     casenumber *row_cnt,
-                                                     size_t *column_cnt);
+static struct source *source_create_casereader (struct casereader *);
+static struct source *clone_source (const struct source *);
 static void source_destroy (struct source *);
-static bool source_rw (struct source *, enum rw_op,
-                       casenumber row, size_t column,
-                       union value *, size_t value_cnt);
 
-static struct sparse_cases *sparse_cases_create (size_t column_cnt);
-static void sparse_cases_destroy (struct sparse_cases *);
-static bool sparse_cases_rw (struct sparse_cases *, enum rw_op,
+static bool source_read (const struct source *, 
                              casenumber row, size_t column,
                              union value *, size_t value_cnt);
+static bool source_write (struct source *, 
+                          casenumber row, size_t column,
+                          const union value *, size_t value_cnt);
+static bool source_write_columns (struct source *, size_t start_column,
+                                  const union value[], size_t value_cnt);
+static bool source_has_backing (const struct source *);
+static void source_increase_use (struct source *, size_t delta);
+static void source_decrease_use (struct source *, size_t delta);
+static bool source_in_use (const struct source *);
 
 /* A datasheet is internally composed from a set of data files,
    called "sources".  The sources that make up a datasheet must
@@ -101,17 +106,17 @@
     struct axis *rows;
     struct range_map sources;
 
-    /* Minimum number of columns to put in a new source we need
-       new columns and none are free.  By increasing it
-       exponentially whenever we add a new source, we can keep
-       the number of file descriptors needed by the datasheet to
-       a minimum, reducing the likelihood of running out. */
+    /* Minimum number of columns to put in a new source when we
+       need new columns and none are free.  We double it whenever
+       we add a new source to keep the number of file descriptors
+       needed by the datasheet to a minimum, reducing the
+       likelihood of running out. */
     unsigned column_min_alloc;
   };
 
 struct source_info 
   {
-    struct range_map_node range;
+    struct range_map_node column_range;
     struct source *source;
   };
 
@@ -119,33 +124,44 @@
 datasheet_create (struct casereader *reader) 
 {
   struct datasheet *ds;
-  struct source_info *si;
-  size_t column_cnt;
-  casenumber row_cnt;
 
-  ds = datasheet_create_empty ();
+  ds = xmalloc (sizeof *ds);
+  ds->columns = axis_create ();
+  ds->rows = axis_create ();
+  range_map_init (&ds->sources);
+  ds->column_min_alloc = 1;
+
+  if (reader != NULL) 
+    {
+      size_t column_cnt = casereader_get_value_cnt (reader);
+      casenumber row_cnt = casereader_count_cases (reader);
+      unsigned long int column_start, row_start;
+      struct source_info *si;
 
   /* Add source_info for READER. */
   si = xmalloc (sizeof *si);
-  si->source = source_create_from_casereader (reader, &row_cnt, &column_cnt);
-  range_map_insert (&ds->sources, 0, column_cnt, &si->range);
+      si->source = source_create_casereader (reader);
+      source_increase_use (si->source, column_cnt);
+      range_map_insert (&ds->sources, 0, column_cnt, &si->column_range);
 
   /* Add column and row mappings for READER. */
-  axis_insert (ds->columns, 0, 0, column_cnt);
-  axis_insert (ds->rows, 0, 0, row_cnt);
+      axis_extend (ds->columns, column_cnt, &column_start);
+      axis_insert (ds->columns, 0, column_start, column_cnt);
+      axis_extend (ds->rows, row_cnt, &row_start);
+      axis_insert (ds->rows, 0, row_start, row_cnt);
+  
+      return ds;
+    }
   
   return ds;
 }
 
-struct datasheet *
-datasheet_create_empty (void) 
+static void
+free_source_info (struct datasheet *ds, struct source_info *si) 
 {
-  struct datasheet *ds = xmalloc (sizeof *ds);
-  ds->columns = axis_create ();
-  ds->rows = axis_create ();
-  range_map_init (&ds->sources);
-  ds->column_min_alloc = 1;
-  return ds;
+  range_map_delete (&ds->sources, &si->column_range);
+  source_destroy (si->source);
+  free (si);
 }
 
 void
@@ -159,33 +175,30 @@
   while (!range_map_is_empty (&ds->sources)) 
     {
       struct range_map_node *r = range_map_first (&ds->sources);
-      struct source_info *si = range_map_data (r, struct source_info, range);
-      range_map_delete (&ds->sources, r);
-      source_destroy (si->source);
-      free (si);
+      struct source_info *si
+        = range_map_data (r, struct source_info, column_range);
+      free_source_info (ds, si);
     }
   free (ds);
 }
 
 casenumber
-datasheet_get_case_cnt (const struct datasheet *ds) 
+datasheet_get_row_cnt (const struct datasheet *ds) 
 {
   return axis_get_size (ds->rows);
 }
 
 size_t
-datasheet_get_value_cnt (const struct datasheet *ds) 
+datasheet_get_column_cnt (const struct datasheet *ds) 
 {
   return axis_get_size (ds->columns);
 }
 
 void
-datasheet_insert_values (struct datasheet *ds,
+datasheet_insert_columns (struct datasheet *ds,
                          const union value init_values[], size_t cnt,
                          size_t before) 
 {
-  casenumber row_cnt = datasheet_get_case_cnt (ds);
-
   while (cnt > 0) 
     {
       unsigned long first_phy;
@@ -195,37 +208,37 @@
         {
           struct source_info *si;
 
-          axis_extend (ds->columns, MAX (cnt, ds->column_min_alloc));
-          if (!axis_allocate (ds->columns, cnt, &first_phy, &phy_cnt))
-            NOT_REACHED ();
-          if (ds->column_min_alloc < 65536)
-            ds->column_min_alloc *= 2;
+          phy_cnt = MAX (cnt, ds->column_min_alloc);
+          axis_extend (ds->columns, phy_cnt, &first_phy);
+          ds->column_min_alloc = MIN (65536, ds->column_min_alloc * 2);
 
           si = xmalloc (sizeof *si);
           si->source = source_create_empty (phy_cnt);
-          range_map_insert (&ds->sources, first_phy, phy_cnt, &si->range);
+          range_map_insert (&ds->sources, first_phy, phy_cnt,
+                            &si->column_range);
+          if (phy_cnt > cnt) 
+            {
+              axis_make_available (ds->columns, first_phy + cnt,
+                                   phy_cnt - cnt);
+              phy_cnt = cnt; 
+            }
         }
 
       while (phy_cnt > 0)
         {
           struct range_map_node *r;
           struct source_info *s;
-          size_t source_col_ofs;
           size_t source_cnt;
-          casenumber lrow;
 
           r = range_map_lookup (&ds->sources, first_phy);
-          s = range_map_data (r, struct source_info, range);
+          s = range_map_data (r, struct source_info, column_range);
           source_cnt = MIN (phy_cnt, range_map_node_get_end (r) - first_phy);
           axis_insert (ds->columns, before, first_phy, source_cnt);
 
-          source_col_ofs = first_phy - range_map_node_get_start (r);
-          for (lrow = 0; lrow < row_cnt; lrow++) 
-            {
-              casenumber prow = axis_map (ds->rows, lrow);
-              source_rw (s->source, true, prow, source_col_ofs,
-                         (union value *) init_values, source_cnt);
-            }
+          source_write_columns (s->source,
+                                first_phy - range_map_node_get_start (r),
+                                init_values, source_cnt);
+          source_increase_use (s->source, source_cnt);
 
           phy_cnt -= source_cnt;
           first_phy += source_cnt;
@@ -237,19 +250,46 @@
 }
 
 void
-datasheet_delete_values (struct datasheet *ds, size_t start, size_t cnt) 
+datasheet_delete_columns (struct datasheet *ds, size_t start, size_t cnt) 
 {
+  size_t lcol;
+
+  /* Free up columns for reuse. */
+  for (lcol = start; lcol < start + cnt; lcol++)
+    {
+      size_t pcol = axis_map (ds->columns, lcol);
+      struct range_map_node *r = range_map_lookup (&ds->sources, pcol);
+      struct source_info *si = range_map_data (r, struct source_info,
+                                               column_range);
+
+      source_decrease_use (si->source, 1);
+      if (source_has_backing (si->source))
+        {
+          if (!source_in_use (si->source))
+            free_source_info (ds, si);
+        }
+      else 
+        axis_make_available (ds->columns, pcol, 1);
+    }
+
+  /* Remove columns from logical-to-physical mapping. */
   axis_remove (ds->columns, start, cnt);
 }
 
 void
-datasheet_move_values (struct datasheet *ds,
+datasheet_move_columns (struct datasheet *ds,
                        size_t old_start, size_t new_start,
                        size_t cnt) 
 {
   axis_move (ds->columns, old_start, new_start, cnt);
 }
 
+enum rw_op 
+  {
+    OP_READ,
+    OP_WRITE
+  };
+
 static bool
 rw_case (struct datasheet *ds, enum rw_op op,
          casenumber lrow, size_t start_column, size_t column_cnt,
@@ -258,29 +298,37 @@
   casenumber prow;
   size_t lcol;
 
+  assert (lrow < datasheet_get_row_cnt (ds));
+  assert (column_cnt <= datasheet_get_column_cnt (ds));
+  assert (start_column + column_cnt <= datasheet_get_column_cnt (ds));
+
   prow = axis_map (ds->rows, lrow);
-  for (lcol = 0; lcol < column_cnt; lcol++)
+  for (lcol = start_column; lcol < start_column + column_cnt; lcol++)
     {
       size_t pcol;
       struct range_map_node *r;
       struct source_info *s;
 
-      pcol = axis_map (ds->columns, lcol + start_column);
-      r = range_map_lookup (&ds->sources, lcol + start_column);
-      s = range_map_data (r, struct source_info, range);
-      source_rw (s->source, op,
-                 prow, pcol - range_map_node_get_start (r),
-                 &data[lcol], 1);
+      pcol = axis_map (ds->columns, lcol);
+      r = range_map_lookup (&ds->sources, pcol);
+      s = range_map_data (r, struct source_info, column_range);
+      if (op == OP_READ)
+        source_read (s->source, prow, pcol - range_map_node_get_start (r),
+                     data++, 1);
+      else
+        source_write (s->source, prow, pcol - range_map_node_get_start (r),
+                     data++, 1);
     }
   return true;
 }
 
 bool
-datasheet_get_case (struct datasheet *ds, casenumber lrow, struct ccase *c) 
+datasheet_get_row (const struct datasheet *ds, casenumber row, struct ccase *c)
 {
-  size_t column_cnt = datasheet_get_value_cnt (ds);
+  size_t column_cnt = datasheet_get_column_cnt (ds);
   case_create (c, column_cnt);
-  if (rw_case (ds, OP_READ, lrow, 0, column_cnt, case_data_all_rw (c)))
+  if (rw_case ((struct datasheet *) ds, OP_READ,
+               row, 0, column_cnt, case_data_all_rw (c)))
     return true;
   else 
     {
@@ -290,33 +338,33 @@
 }
      
 bool
-datasheet_put_case (struct datasheet *ds, casenumber lrow, struct ccase *c)
+datasheet_put_row (struct datasheet *ds, casenumber row, struct ccase *c)
 {
-  size_t column_cnt = datasheet_get_value_cnt (ds);
-  bool ok = rw_case (ds, OP_WRITE, lrow, 0, column_cnt,
+  size_t column_cnt = datasheet_get_column_cnt (ds);
+  bool ok = rw_case (ds, OP_WRITE, row, 0, column_cnt,
                      (union value *) case_data_all (c));
   case_destroy (c);
   return ok;
 }
 
 bool
-datasheet_get_value (struct datasheet *ds, casenumber lrow, size_t column,
+datasheet_get_value (const struct datasheet *ds, casenumber row, size_t column,
                      union value *value, int width) 
 {
-  return rw_case (ds, OP_READ, lrow, column, value_cnt_from_width (width),
-                  value);
+  return rw_case ((struct datasheet *) ds,
+                  OP_READ, row, column, value_cnt_from_width (width), value);
 }
 
 bool
-datasheet_put_value (struct datasheet *ds, casenumber lrow, size_t column,
+datasheet_put_value (struct datasheet *ds, casenumber row, size_t column,
                      const union value *value, int width)
 {
-  return rw_case (ds, OP_WRITE, lrow, column, value_cnt_from_width (width),
+  return rw_case (ds, OP_WRITE, row, column, value_cnt_from_width (width),
                   (union value *) value);
 }
 
 bool
-datasheet_insert_cases (struct datasheet *ds,
+datasheet_insert_rows (struct datasheet *ds,
                         casenumber before, struct ccase *c,
                         casenumber cnt) 
 {
@@ -329,18 +377,17 @@
 
       if (!axis_allocate (ds->rows, cnt, &first_phy, &phy_cnt))
         {
-          axis_extend (ds->rows, cnt);
-          if (!axis_allocate (ds->rows, cnt, &first_phy, &phy_cnt))
-            NOT_REACHED ();
+          phy_cnt = cnt;
+          axis_extend (ds->rows, cnt, &first_phy); 
          }
 
       axis_insert (ds->rows, before, first_phy, phy_cnt);
       for (i = 0; i < phy_cnt; i++)
-        if (!datasheet_put_case (ds, before + i, &c[i]))
+        if (!datasheet_put_row (ds, before + i, &c[i]))
           {
             while (++i < cnt)
               case_destroy (&c[i]);
-            datasheet_delete_cases (ds, before - added, phy_cnt + added);
+            datasheet_delete_rows (ds, before - added, phy_cnt + added);
             return false;
           }
 
@@ -353,12 +400,28 @@
 }
 
 void
-datasheet_delete_cases (struct datasheet *ds,
+datasheet_delete_rows (struct datasheet *ds,
                         casenumber first, casenumber cnt) 
 {
+  size_t lrow;
+
+  /* Free up rows for reuse.
+     FIXME: optimize. */
+  for (lrow = first; lrow < first + cnt; lrow++)
+    axis_make_available (ds->rows, axis_map (ds->rows, lrow), 1);
+
+  /* Remove rows from logical-to-physical mapping. */
   axis_remove (ds->rows, first, cnt);
 }
 
+void
+datasheet_move_rows (struct datasheet *ds,
+                     size_t old_start, size_t new_start,
+                     size_t cnt) 
+{
+  axis_move (ds->rows, old_start, new_start, cnt);
+}
+
 static const struct buffered_reader_class datasheet_reader_class;
 
 static struct datasheet *
@@ -373,8 +436,8 @@
 datasheet_make_reader (struct datasheet *ds) 
 {
   ds = datasheet_rename (ds);
-  return buffered_reader_create (datasheet_get_value_cnt (ds),
-                                 datasheet_get_case_cnt (ds),
+  return buffered_reader_create (datasheet_get_column_cnt (ds),
+                                 datasheet_get_row_cnt (ds),
                                  &datasheet_reader_class, ds);
 }
 
@@ -382,10 +445,10 @@
 datasheet_reader_read (void *ds_, casenumber case_idx, struct ccase *c) 
 {
   struct datasheet *ds = ds_;
-  if (case_idx >= datasheet_get_case_cnt (ds))
+  if (case_idx >= datasheet_get_row_cnt (ds))
     return false;
   else
-    return datasheet_get_case (ds, case_idx, c);
+    return datasheet_get_row (ds, case_idx, c);
 }
 
 static bool
@@ -407,7 +470,7 @@
 datasheet_reader_advance (void *ds_, casenumber case_cnt) 
 {
   struct datasheet *ds = ds_;
-  datasheet_delete_cases (ds, 0, case_cnt);
+  datasheet_delete_rows (ds, 0, case_cnt);
 }
 
 static const struct buffered_reader_class datasheet_reader_class = 
@@ -437,7 +500,9 @@
   return tower_data (node, struct axis_group, logical);
 }
 
-struct axis *
+static struct tower_node *make_axis_group (unsigned long int phy_start);
+
+static struct axis *
 axis_create (void) 
 {
   struct axis *axis = xmalloc (sizeof *axis);
@@ -447,7 +512,60 @@
   return axis;
 }
 
-void
+static struct axis *
+clone_axis (const struct axis *old) 
+{
+  const struct tower_node *node;
+  struct axis *new;
+
+  new = xmalloc (sizeof *new);
+  tower_init (&new->log_to_phy);
+  new->available = range_set_clone (old->available, NULL);
+  new->phy_size = old->phy_size;
+
+  for (node = tower_first (&old->log_to_phy); node != NULL;
+       node = tower_next (&old->log_to_phy, node)) 
+    {
+      unsigned long int size = tower_node_get_height (node);
+      struct axis_group *group = tower_data (node, struct axis_group, logical);
+      tower_insert (&new->log_to_phy, size, make_axis_group (group->phy_start),
+                    NULL);
+    }
+
+  return new;
+}
+
+static void
+hash_axis (const struct axis *axis, struct md4_ctx *ctx) 
+{
+  const struct tower_node *tn;
+  const struct range_set_node *rsn;
+
+  for (tn = tower_first (&axis->log_to_phy); tn != NULL;
+       tn = tower_next (&axis->log_to_phy, tn)) 
+    {
+      struct axis_group *group = tower_data (tn, struct axis_group, logical);
+      unsigned long int phy_start = group->phy_start;
+      unsigned long int size = tower_node_get_height (tn);
+
+      md4_process_bytes (&phy_start, sizeof phy_start, ctx);
+      md4_process_bytes (&size, sizeof size, ctx);
+    }
+  
+  for (rsn = range_set_first (axis->available); rsn != NULL;
+       rsn = range_set_next (axis->available, rsn)) 
+    {
+      unsigned long int start = range_set_node_get_start (rsn);
+      unsigned long int end = range_set_node_get_end (rsn);
+      
+      md4_process_bytes (&start, sizeof start, ctx);
+      md4_process_bytes (&end, sizeof end, ctx);
+    }
+
+  md4_process_bytes (&axis->phy_size, sizeof axis->phy_size, ctx);
+}
+
+static void
 axis_destroy (struct axis *axis) 
 {
   if (axis == NULL)
@@ -466,7 +584,7 @@
   free (axis);
 }
 
-bool
+static bool
 axis_allocate (struct axis *axis, unsigned long int request,
                unsigned long int *start,
                unsigned long int *width) 
@@ -474,14 +592,22 @@
   return range_set_allocate (axis->available, request, start, width);
 }
 
-void
-axis_extend (struct axis *axis, unsigned long int width) 
+static void
+axis_make_available (struct axis *axis,
+                     unsigned long int start, unsigned long int width) 
 {
-  range_set_insert (axis->available, axis->phy_size, width);
+  range_set_insert (axis->available, start, width);
+}
+
+static void
+axis_extend (struct axis *axis, unsigned long int width,
+             unsigned long int *start) 
+{
+  *start = axis->phy_size;
   axis->phy_size += width;
 }
 
-unsigned long int
+static unsigned long int
 axis_map (const struct axis *axis, unsigned long log_pos) 
 {
   struct tower_node *node;
@@ -493,7 +619,7 @@
   return group->phy_start + (log_pos - group_start);
 }
 
-unsigned long
+static unsigned long
 axis_get_size (const struct axis *axis) 
 {
   return tower_height (&axis->log_to_phy);
@@ -514,10 +640,10 @@
   struct tower_node *group_node;
   struct axis_group *group;
 
-  group_node = tower_lookup (&axis->log_to_phy, where, &group_start);
-  if (group_node == NULL)
+  if (where >= tower_height (&axis->log_to_phy))
     return NULL;
   
+  group_node = tower_lookup (&axis->log_to_phy, where, &group_start);
   group = axis_group_from_tower_node (group_node);
   if (where > group_start) 
     {
@@ -533,7 +659,75 @@
     return &group->logical;
 }
 
-void
+static void
+merge_axis_nodes (struct axis *axis, struct tower_node *node,
+                  struct tower_node **other_node)
+{
+  struct tower *t = &axis->log_to_phy;
+  struct axis_group *group;
+  struct tower_node *next, *prev;
+  
+  if (node == NULL) 
+    node = tower_last (t);
+  if (node == NULL)
+    return;
+  group = axis_group_from_tower_node (node);
+
+  next = tower_next (t, node);
+  if (next != NULL) 
+    {
+      struct axis_group *next_group = axis_group_from_tower_node (next);
+      unsigned long this_height = tower_node_get_height (node);
+      
+      if (group->phy_start + this_height == next_group->phy_start)
+        {
+          unsigned long next_height = tower_node_get_height (next);
+          tower_resize (t, node, this_height + next_height);
+          if (other_node != NULL && *other_node == next)
+            *other_node = tower_next (t, *other_node);
+          tower_delete (t, next);
+          free (next_group);
+        }
+    }
+
+  prev = tower_prev (t, node);
+  if (prev != NULL) 
+    {
+      struct axis_group *prev_group = axis_group_from_tower_node (prev);
+      unsigned long prev_height = tower_node_get_height (prev);
+      
+      if (prev_group->phy_start + prev_height == group->phy_start)
+        {
+          unsigned long this_height = tower_node_get_height (node);
+          group->phy_start = prev_group->phy_start;
+          tower_resize (t, node, this_height + prev_height);
+          if (other_node != NULL && *other_node == prev)
+            *other_node = tower_next (t, *other_node);
+          tower_delete (t, prev);
+          free (prev_group);
+        }
+    }
+}
+
+static void
+check_axis_merged (const struct axis *axis UNUSED) 
+{
+#if 0//ASSERT_LEVEL >= 10
+  struct tower_node *prev, *node;
+  
+  for (prev = NULL, node = tower_first (&axis->log_to_phy); node != NULL;
+       prev = node, node = tower_next (&axis->log_to_phy, node))
+    if (prev != NULL) 
+      {
+        struct axis_group *prev_group = axis_group_from_tower_node (prev);
+        unsigned long prev_height = tower_node_get_height (prev);
+        struct axis_group *node_group = axis_group_from_tower_node (node);
+        assert (prev_group->phy_start + prev_height != node_group->phy_start);
+      }
+#endif
+}
+
+static void
 axis_insert (struct axis *axis,
              unsigned long int log_start, unsigned long int phy_start,
              unsigned long int cnt) 
@@ -541,9 +735,11 @@
   struct tower_node *before = split_axis (axis, log_start);
   struct tower_node *new = make_axis_group (phy_start);
   tower_insert (&axis->log_to_phy, cnt, new, before);
+  merge_axis_nodes (axis, new, NULL);
+  check_axis_merged (axis);
 }
 
-void
+static void
 axis_remove (struct axis *axis,
              unsigned long int start, unsigned long int cnt) 
 {
@@ -556,10 +752,12 @@
           next = tower_delete (&axis->log_to_phy, cur);
           free (axis_group_from_tower_node (cur)); 
         }
+      merge_axis_nodes (axis, last, NULL);
+      check_axis_merged (axis);
     }
 }
 
-void
+static void
 axis_move (struct axis *axis,
            unsigned long int old_start, unsigned long int new_start,
            unsigned long int cnt) 
@@ -567,6 +765,7 @@
   if (cnt > 0 && old_start != new_start) 
     {
       struct tower_node *old_first, *old_last, *new_first;
+      struct tower_node *merge1, *merge2;
       struct tower tmp_array;
 
       old_first = split_axis (axis, old_start);
@@ -574,61 +773,91 @@
       tower_init (&tmp_array);
       tower_splice (&tmp_array, NULL,
                         &axis->log_to_phy, old_first, old_last);
+      merge_axis_nodes (axis, old_last, NULL);
+
+      check_axis_merged (axis);
 
       new_first = split_axis (axis, new_start);
+      merge1 = tower_first (&tmp_array);
+      merge2 = tower_last (&tmp_array);
+      if (merge2 == merge1)
+        merge2 = NULL;
       tower_splice (&axis->log_to_phy, new_first, &tmp_array, old_first, NULL);
+      merge_axis_nodes (axis, merge1, &merge2);
+      merge_axis_nodes (axis, merge2, NULL);
+
+      check_axis_merged (axis);
     }
 }
 
-/* A source. */
-struct source
+/* Type of a source. */
+enum backing_type 
   {
-    struct backing *backing;
-    struct sparse_cases *overlay;
+    SOURCE_CASES,
+    SOURCE_CASEREADER
   };
 
-struct backing 
+/* A source. */
+struct source
   {
-    struct casereader *reader;
-    struct range_set *rows;
+    size_t columns_used;
+    struct sparse_cases *data;
+    struct casereader *backing;
   };
 
-struct source *
+static struct source *
 source_create_empty (size_t column_cnt) 
 {
   struct source *source = xmalloc (sizeof *source);
+  source->columns_used = 0;
+  source->data = sparse_cases_create (column_cnt);
   source->backing = NULL;
-  source->overlay = sparse_cases_create (column_cnt);
   return source;
 }
 
-struct source *
-source_create_from_casereader (struct casereader *reader,
-                               casenumber *row_cnt, size_t *column_cnt) 
+static struct source *
+source_create_casereader (struct casereader *reader) 
 {
-  struct source *source;
+  struct source *source
+    = source_create_empty (casereader_get_value_cnt (reader));
+  source->backing = reader;
+  return source;
+}
 
-  *column_cnt = casereader_get_value_cnt (reader);
-  *row_cnt = casereader_get_case_cnt (reader);
-  if (*row_cnt == CASENUMBER_INVALID) 
+/* For testing purposes only. */
+static struct source *
+clone_source (const struct source *old) 
+{
+  struct source *new = xmalloc (sizeof *new);
+  new->columns_used = old->columns_used;
+  new->data = sparse_cases_clone (old->data);
+  new->backing = old->backing != NULL ? casereader_clone (old->backing) : NULL;
+  if (new->data == NULL)
     {
-      struct casereader *clone;
-      struct ccase c;
-
-      *row_cnt = 0;
-      clone = casereader_clone (reader);
-      for (; casereader_read (clone, &c); case_destroy (&c)) 
-        (*row_cnt)++;
-      casereader_destroy (clone);
+      source_destroy (new);
+      new = NULL;
     }
+  return new;
+}
 
-  source = source_create_empty (*column_cnt);
-  source->backing = xmalloc (sizeof *source->backing);
-  source->backing->reader = reader;
-  source->backing->rows = range_set_create ();
-  range_set_insert (source->backing->rows, 0, *row_cnt);
+static void
+source_increase_use (struct source *source, size_t delta) 
+{
+  source->columns_used += delta;
+  assert (source->columns_used <= sparse_cases_get_value_cnt (source->data));
+}
 
-  return source;
+static void
+source_decrease_use (struct source *source, size_t delta) 
+{
+  source->columns_used -= delta;
+  assert (source->columns_used <= sparse_cases_get_value_cnt (source->data));
+}
+
+static bool
+source_in_use (const struct source *source) 
+{
+  return source->columns_used > 0;
 }
 
 static void
@@ -636,190 +865,417 @@
 {
   if (source != NULL) 
     {
-      if (source->backing != NULL) 
-        {
-          casereader_destroy (source->backing->reader);
-          range_set_destroy (source->backing->rows);
-          free (source->backing);
-        }
-      sparse_cases_destroy (source->overlay);
+      sparse_cases_destroy (source->data);
+      casereader_destroy (source->backing);
       free (source);
     }
 }
 
-bool
-source_rw (struct source *source, enum rw_op op,
+static bool
+source_read (const struct source *source, 
            casenumber row, size_t column,
            union value *values, size_t value_cnt) 
 {
-  assert (op == OP_READ || op == OP_WRITE);
-
-  if (source->backing != NULL
-      && range_set_contains (source->backing->rows, row)) 
+  if (source->backing == NULL || sparse_cases_contains_row (source->data, row))
+    return sparse_cases_read (source->data, row, column, values, value_cnt);
+  else
     {
-      /* Get the case from the reader. */
       struct ccase c;
-      if (!casereader_peek (source->backing->reader, row, &c))
-        return false;
+      bool ok;
 
-      if (op == OP_READ) 
+      assert (source->backing != NULL);
+      ok = casereader_peek (source->backing, row, &c);
+      if (ok) 
         {
-          /* Get the values from the case. */
-          size_t i;
-          for (i = 0; i < value_cnt; i++)
-            values[i] = *case_data_idx (&c, column + i);
+          case_copy_out (&c, column, values, value_cnt);
           case_destroy (&c);
-          return true;
         }
+      return ok;
+    }
+}
+
+static bool
+source_write (struct source *source, 
+              casenumber row, size_t column,
+              const union value *values, size_t value_cnt) 
+{
+  size_t column_cnt = sparse_cases_get_value_cnt (source->data);
+  bool ok;
+
+  if (source->backing == NULL
+      || (column == 0 && value_cnt == column_cnt)
+      || sparse_cases_contains_row (source->data, row)) 
+    ok = sparse_cases_write (source->data, row, column, values, value_cnt);
       else 
         {
-          /* Copy the case to the overlay, remove it from the
-             backing, and fall through to the "no backing"
-             case. */
-          sparse_cases_rw (source->overlay, OP_WRITE, row, 0, 
-                           (union value *)case_data_all (&c),
-                           case_get_value_cnt (&c));
-          case_destroy (&c);
-          range_set_delete (source->backing->rows, row, 1);
-          if (range_set_is_empty (source->backing->rows)) 
+      struct ccase c;
+      ok = casereader_peek (source->backing, row, &c);
+      if (ok) 
             {
-              casereader_destroy (source->backing->reader);
-              range_set_destroy (source->backing->rows);
-              free (source->backing);
-              source->backing = NULL;
-            }
+          case_copy_in (&c, column, values, value_cnt);
+          ok = sparse_cases_write (source->data, row, 0,
+                                   case_data_all (&c), column_cnt); 
+          case_destroy (&c);
         }
     }
+  return ok;
+}
+
+static bool
+source_write_columns (struct source *source, size_t start_column,
+                      const union value values[], size_t value_cnt) 
+{
+  /* We don't support backing != NULL because (1) it's harder and
+     (2) source_write_columns is only called by
+     datasheet_insert_columns, which doesn't reuse columns from
+     sources that are backed by casereaders. */
+  assert (source->backing == NULL);
 
-  return sparse_cases_rw (source->overlay, op, row, column, values, value_cnt);
+  return sparse_cases_write_columns (source->data, start_column,
+                                     values, value_cnt);
 }
 
-struct sparse_cases 
-  {
-    size_t column_cnt;
-    casenumber max_memory_cases;
-    struct sparse_array *memory;
-    struct case_tmpfile *disk;
-  };
+static bool
+source_has_backing (const struct source *s) 
+{
+  return s->backing != NULL;
+}
+
+#define MAX_ROWS 4
+#define MAX_COLS 4
 
-static struct sparse_cases *
-sparse_cases_create (size_t column_cnt) 
+static unsigned int
+hash_datasheet (const struct datasheet *ds) 
 {
-  struct sparse_cases *sc = xmalloc (sizeof *sc);
-  sc->column_cnt = column_cnt;
-  sc->max_memory_cases = get_workspace_cases (column_cnt);
-  sc->memory = sparse_array_create (NULL, sizeof (struct ccase));
-  sc->disk = NULL;
-  return sc;
+  unsigned int hash[DIV_RND_UP (20, sizeof (unsigned int))];
+  struct md4_ctx ctx;
+  struct range_map_node *r;
+
+  md4_init_ctx (&ctx);
+  hash_axis (ds->columns, &ctx);
+  hash_axis (ds->rows, &ctx);
+  for (r = range_map_first (&ds->sources); r != NULL;
+       r = range_map_next (&ds->sources, r)) 
+    {
+      unsigned long int start = range_map_node_get_start (r);
+      unsigned long int end = range_map_node_get_end (r);
+      md4_process_bytes (&start, sizeof start, &ctx);
+      md4_process_bytes (&end, sizeof end, &ctx);
+      /* FIXME: hash anything more? */
+    }
+  md4_process_bytes (&ds->column_min_alloc, sizeof ds->column_min_alloc,
+                      &ctx);
+  md4_finish_ctx (&ctx, hash);
+  return hash[0];
 }
 
 static void
-sparse_cases_destroy (struct sparse_cases *sc) 
+check_datasheet (struct mc *mc, struct datasheet *ds,
+                 double array[MAX_ROWS][MAX_COLS],
+                 size_t row_cnt, size_t column_cnt) 
 {
-  if (sc != NULL) 
+  if (mc_discard_dup_state (mc, hash_datasheet (ds)))
     {
-      if (sc->memory != NULL) 
+      datasheet_destroy (ds);
+      return;
+    }
+      
+  if (row_cnt != datasheet_get_row_cnt (ds))
+    mc_error (mc, "row count (%lu) does not match expected (%zu)",
+              (unsigned long int) datasheet_get_row_cnt (ds), row_cnt);
+  else if (column_cnt != datasheet_get_column_cnt (ds))
+    mc_error (mc, "column count (%lu) does not match expected (%zu)",
+              (unsigned long int) datasheet_get_column_cnt (ds), column_cnt);
+  else 
         {
-          unsigned long int idx;
-          struct ccase *c;
-          for (c = sparse_array_scan (sc->memory, NULL, &idx); c != NULL;
-               c = sparse_array_scan (sc->memory, &idx, &idx)) 
-            case_destroy (c);
-          sparse_array_destroy (sc->memory);
+      size_t row, col;
+
+      for (row = 0; row < row_cnt; row++)
+        for (col = 0; col < column_cnt; col++) 
+          {
+            union value v;
+            if (!datasheet_get_value (ds, row, col, &v, 1))
+              NOT_REACHED ();
+            if (v.f != array[row][col])
+              mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: %g != %g",
+                        row, col, row_cnt, column_cnt, v.f, array[row][col]);
         }
-      case_tmpfile_destroy (sc->disk);
-      free (sc);
     }
+
+  mc_add_state (mc, ds);
 }
 
 static void
-dump_sparse_cases_to_disk (struct sparse_cases *sc) 
+extract_data (const struct datasheet *ds, double data[MAX_ROWS][MAX_COLS])
 {
-  unsigned long int idx;
-  struct ccase *c;
+  size_t column_cnt = datasheet_get_column_cnt (ds);
+  size_t row_cnt = datasheet_get_row_cnt (ds);
+  size_t row, col;
 
-  assert (sc->memory != NULL);
-  assert (sc->disk == NULL);
+  for (row = 0; row < row_cnt; row++)
+    for (col = 0; col < column_cnt; col++) 
+      {
+        union value v;
+        if (!datasheet_get_value (ds, row, col, &v, 1))
+          NOT_REACHED ();
+        data[row][col] = v.f;
+      }
+}
 
-  sc->disk = case_tmpfile_create (sc->column_cnt);
+static void
+clone_model (const struct datasheet *ods, double odata[MAX_ROWS][MAX_COLS],
+             struct datasheet **ds_, double data[MAX_ROWS][MAX_COLS]) 
+{
+  struct datasheet *ds;
+  struct range_map_node *r;
+  size_t row_cnt, column_cnt;
+  size_t row, column;
 
-  for (c = sparse_array_scan (sc->memory, NULL, &idx); c != NULL;
-       c = sparse_array_scan (sc->memory, &idx, &idx)) 
-    case_tmpfile_put (sc->disk, idx, c);
-  sparse_array_destroy (sc->memory);
-  sc->memory = NULL;
+  /* Clone ODS into DS. */
+  ds = *ds_ = xmalloc (sizeof *ds);
+  ds->columns = clone_axis (ods->columns);
+  ds->rows = clone_axis (ods->rows);
+  range_map_init (&ds->sources);
+  for (r = range_map_first (&ods->sources); r != NULL;
+       r = range_map_next (&ods->sources, r)) 
+    {
+      const struct source_info *osi = range_map_data (r, struct source_info,
+                                                      column_range);
+      struct source_info *si = xmalloc (sizeof *si);
+      si->source = clone_source (osi->source);
+      range_map_insert (&ds->sources, range_map_node_get_start (r),
+                        range_map_node_get_width (r), &si->column_range);
+    }
+  ds->column_min_alloc = ods->column_min_alloc;
+
+  /* Clone ODATA into DATA. */
+  row_cnt = datasheet_get_row_cnt (ds);
+  column_cnt = datasheet_get_column_cnt (ds);
+  for (row = 0; row < row_cnt; row++)
+    for (column = 0; column < column_cnt; column++)
+      data[row][column] = odata[row][column];
 }
 
 static void
-case_rw (enum rw_op op, struct ccase *c, size_t c_ofs,
-         union value *values, size_t value_cnt) 
+datasheet_mc_init (struct mc *mc) 
 {
-  size_t i;
-  if (op == OP_WRITE) 
+  struct datasheet_test_params *params = mc_get_aux (mc);
+  struct datasheet *ds;
+
+  if (params->backing_rows == 0 && params->backing_cols == 0) 
     {
-      for (i = 0; i < value_cnt; i++) 
-        *case_data_rw_idx (c, c_ofs + i) = values[i]; 
+      ds = datasheet_create (NULL);
+      mc_name_state (mc, "empty datasheet");
+      check_datasheet (mc, ds, NULL, 0, 0);
     }
   else 
     {
-      for (i = 0; i < value_cnt; i++) 
-        values[i] = *case_data_idx (c, c_ofs + i);
-    }
-}
+      struct casewriter *writer;
+      struct casereader *reader;
+      double data[MAX_ROWS][MAX_COLS];
+      int row;
 
-static bool
-disk_sparse_cases_rw (struct sparse_cases *sc, enum rw_op op,
-                      casenumber row, size_t column,
-                      union value *values, size_t value_cnt)
-{
+      assert (params->backing_rows > 0 && params->backing_rows <= MAX_ROWS);
+      assert (params->backing_cols > 0 && params->backing_cols <= MAX_COLS);
+
+      writer = mem_writer_create (params->backing_cols);
+      for (row = 0; row < params->backing_rows; row++)
+        {
   struct ccase c;
+          int col;
 
-  if (!case_tmpfile_get (sc->disk, row, &c))
-    return false;
-  case_rw (op, &c, column, values, value_cnt);
-  if (op == OP_WRITE)
-    return case_tmpfile_put (sc->disk, row, &c);
-  else 
+          case_create (&c, params->backing_cols);
+          for (col = 0; col < params->backing_cols; col++)
     {
-      case_destroy (&c);
-      return true;
+              double value = params->next_value++;
+              data[row][col] = value;
+              case_data_rw_idx (&c, col)->f = value;
+            }
+          casewriter_write (writer, &c);
+        }
+      reader = casewriter_make_reader (writer);
+      assert (reader != NULL);
+      
+      ds = datasheet_create (reader);
+      mc_name_state (mc, "datasheet with (%d,%d) backing",
+                     params->backing_rows, params->backing_cols);
+      check_datasheet (mc, ds, data,
+                       params->backing_rows, params->backing_cols);
     }
 }
 
-static bool
-memory_sparse_cases_rw (struct sparse_cases *sc, enum rw_op op,
-                        casenumber row, size_t column,
-                        union value *values, size_t value_cnt)
+static void
+datasheet_mc_mutate (struct mc *mc, const void *ods_) 
 {
-  struct ccase *c;
+  struct datasheet_test_params *params = mc_get_aux (mc);
 
-  c = sparse_array_get (sc->memory, row);
-  if (c == NULL) 
+  const struct datasheet *ods = ods_;
+  double odata[MAX_ROWS][MAX_COLS];
+  double data[MAX_ROWS][MAX_COLS];
+  size_t column_cnt = datasheet_get_column_cnt (ods);
+  size_t row_cnt = datasheet_get_row_cnt (ods);
+  size_t pos, new_pos, cnt;
+
+  extract_data (ods, odata);
+
+  for (pos = 0; pos <= column_cnt; pos++)
+    for (cnt = 0; cnt <= params->max_cols - column_cnt; cnt++)
+      if (mc_include_state (mc))
     {
-      if (sparse_array_count (sc->memory) < sc->max_memory_cases)
+          struct datasheet *ds;
+          union value new[MAX_COLS];
+          size_t i, j;
+
+          mc_name_state (mc, "insert %zu columns at %zu", cnt, pos);
+          clone_model (ods, odata, &ds, data);
+
+          for (i = 0; i < cnt; i++) 
+            new[i].f = params->next_value++;
+
+          datasheet_insert_columns (ds, new, cnt, pos);
+        
+          for (i = 0; i < row_cnt; i++) 
         {
-          assert (op == OP_WRITE);
-          c = sparse_array_insert (sc->memory, row);
-          case_create (c, sc->column_cnt);
+              /* We could use an insert_range function here. */
+              memmove (&data[i][pos + cnt], &data[i][pos],
+                       (column_cnt - pos) * sizeof data[i][pos]);
+              for (j = 0; j < cnt; j++)
+                data[i][pos + j] = new[j].f;
         }
-      else 
+
+          check_datasheet (mc, ds, data, row_cnt, column_cnt + cnt);
+        }
+
+  for (pos = 0; pos < column_cnt; pos++)
+    for (cnt = 0; cnt < column_cnt - pos; cnt++) 
+      if (mc_include_state (mc))
         {
-          dump_sparse_cases_to_disk (sc);
-          return disk_sparse_cases_rw (sc, op, row, column, values, value_cnt);
+          struct datasheet *ds;
+          size_t i;
+
+          mc_name_state (mc, "delete %zu columns at %zu", cnt, pos);
+          clone_model (ods, odata, &ds, data);
+
+          datasheet_delete_columns (ds, pos, cnt);
+        
+          for (i = 0; i < row_cnt; i++)
+            remove_range (&data[i], column_cnt, sizeof *data[i], pos, cnt);
+
+          check_datasheet (mc, ds, data, row_cnt, column_cnt - cnt);
         }
+
+  for (pos = 0; pos < column_cnt; pos++)
+    for (cnt = 0; cnt < column_cnt - pos; cnt++)
+      for (new_pos = 0; new_pos < column_cnt - cnt; new_pos++)
+        if (mc_include_state (mc))
+          {
+            struct datasheet *ds;
+            size_t i;
+
+            clone_model (ods, odata, &ds, data);
+            mc_name_state (mc, "move %zu columns from %zu to %zu",
+                           cnt, pos, new_pos);
+
+            datasheet_move_columns (ds, pos, new_pos, cnt);
+        
+            for (i = 0; i < row_cnt; i++)
+              move_range (&data[i], column_cnt, sizeof data[i][0],
+                          pos, new_pos, cnt);
+
+            check_datasheet (mc, ds, data, row_cnt, column_cnt);
     }
   
-  case_rw (op, c, column, values, value_cnt);
+  for (pos = 0; pos <= row_cnt; pos++)
+    for (cnt = 0; cnt <= params->max_rows - row_cnt; cnt++) 
+      if (mc_include_state (mc))
+        {
+          struct datasheet *ds;
+          struct ccase c[MAX_ROWS];
+          size_t i, j;
   
-  return true;
+          clone_model (ods, odata, &ds, data);
+          mc_name_state (mc, "insert %zu rows at %zu", cnt, pos);
+
+          for (i = 0; i < cnt; i++) 
+            {
+              case_create (&c[i], column_cnt);
+              for (j = 0; j < column_cnt; j++)
+                case_data_rw_idx (&c[i], j)->f = params->next_value++;
+            }
+        
+          /* We could use an insert_range function here. */
+          memmove (&data[pos + cnt], &data[pos],
+                   sizeof (double[MAX_COLS]) * (row_cnt - pos));
+          for (i = 0; i < cnt; i++) 
+            for (j = 0; j < column_cnt; j++)
+              data[i + pos][j] = case_num_idx (&c[i], j);
+
+          if (!datasheet_insert_rows (ds, pos, c, cnt))
+            NOT_REACHED ();
+
+          check_datasheet (mc, ds, data, row_cnt + cnt, column_cnt);
+        }
+
+  for (pos = 0; pos < row_cnt; pos++)
+    for (cnt = 0; cnt < row_cnt - pos; cnt++) 
+      if (mc_include_state (mc))
+        {
+          struct datasheet *ds;
+
+          clone_model (ods, odata, &ds, data);
+          mc_name_state (mc, "delete %zu rows at %zu", cnt, pos);
+
+          datasheet_delete_rows (ds, pos, cnt);
+
+          remove_range (&data[0], row_cnt, sizeof data[0], pos, cnt);
+
+          check_datasheet (mc, ds, data, row_cnt - cnt, column_cnt);
+        }
+
+  for (pos = 0; pos < row_cnt; pos++)
+    for (cnt = 0; cnt < row_cnt - pos; cnt++)
+      for (new_pos = 0; new_pos < row_cnt - cnt; new_pos++)
+        if (mc_include_state (mc))
+          {
+            struct datasheet *ds;
+
+            clone_model (ods, odata, &ds, data);
+            mc_name_state (mc,
+                           "move %zu rows from %zu to %zu", cnt, pos, new_pos);
+
+            datasheet_move_rows (ds, pos, new_pos, cnt);
+        
+            move_range (&data[0], row_cnt, sizeof data[0],
+                        pos, new_pos, cnt);
+
+            check_datasheet (mc, ds, data, row_cnt, column_cnt);
+          }      
 }
 
-static bool
-sparse_cases_rw (struct sparse_cases *sc, enum rw_op op,
-                 casenumber row, size_t column,
-                 union value *values, size_t value_cnt)
+static void
+datasheet_mc_destroy (void *ds_) 
 {
-  return (sc->memory != NULL 
-          ? memory_sparse_cases_rw (sc, op, row, column, values, value_cnt)
-          : disk_sparse_cases_rw (sc, op, row, column, values, value_cnt));
+  struct datasheet *ds = ds_;
+  datasheet_destroy (ds);
+}
+
+struct mc_results *
+datasheet_test (struct mc_options *options, void *params_) 
+{
+  struct datasheet_test_params *params = params_;
+  static const struct mc_class datasheet_mc_class =
+    {
+      datasheet_mc_init,
+      datasheet_mc_mutate,
+      datasheet_mc_destroy,
+    };
+
+  params->next_value = 0;
+  params->max_rows = MIN (params->max_rows, MAX_ROWS);
+  params->max_cols = MIN (params->max_cols, MAX_COLS);
+  params->backing_rows = MIN (params->backing_rows, params->max_rows);
+  params->backing_cols = MIN (params->backing_cols, params->max_cols);
+
+  mc_options_set_aux (options, params);
+  return mc_run (&datasheet_mc_class, options);
 }

Index: src/data/datasheet.h
===================================================================
RCS file: /cvsroot/pspp/pspp/src/data/Attic/datasheet.h,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/data/datasheet.h        19 Mar 2007 21:36:24 -0000      1.1.2.1
+++ src/data/datasheet.h        14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -29,38 +29,55 @@
    retrieval, as well as adding, removing, and rearranging both
    rows and columns.  */
 
-struct datasheet *datasheet_create_empty (void);
 struct datasheet *datasheet_create (struct casereader *);
 void datasheet_destroy (struct datasheet *);
-struct casereader *datasheet_make_reader (struct datasheet *);
 
-casenumber datasheet_get_case_cnt (const struct datasheet *);
-size_t datasheet_get_value_cnt (const struct datasheet *);
+struct casereader *datasheet_make_reader (struct datasheet *);
 
 /* Columns. */
-void datasheet_insert_values (struct datasheet *,
+size_t datasheet_get_column_cnt (const struct datasheet *);
+void datasheet_insert_columns (struct datasheet *,
                               const union value[], size_t cnt,
                               size_t before);
-void datasheet_delete_values (struct datasheet *, size_t start, size_t cnt);
-void datasheet_move_values (struct datasheet *,
+void datasheet_delete_columns (struct datasheet *, size_t start, size_t cnt);
+void datasheet_move_columns (struct datasheet *,
                             size_t old_start, size_t new_start,
                             size_t cnt);
-void datasheet_reorder_values (struct datasheet *,
-                               size_t *ordering, size_t cnt);
 
 /* Rows. */
-bool datasheet_insert_cases (struct datasheet *,
+casenumber datasheet_get_row_cnt (const struct datasheet *);
+bool datasheet_insert_rows (struct datasheet *,
                              casenumber before, struct ccase *,
                              casenumber cnt);
-void datasheet_delete_cases (struct datasheet *,
+void datasheet_delete_rows (struct datasheet *,
                              casenumber first, casenumber cnt);
+void datasheet_move_rows (struct datasheet *,
+                          size_t old_start, size_t new_start,
+                          size_t cnt);
 
 /* Data. */
-bool datasheet_get_case (struct datasheet *, casenumber, struct ccase *);
-bool datasheet_put_case (struct datasheet *, casenumber, struct ccase *);
-bool datasheet_get_value (struct datasheet *, casenumber, size_t column,
+bool datasheet_get_row (const struct datasheet *, casenumber, struct ccase *);
+bool datasheet_put_row (struct datasheet *, casenumber, struct ccase *);
+bool datasheet_get_value (const struct datasheet *, casenumber, size_t column,
                           union value *, int width);
 bool datasheet_put_value (struct datasheet *, casenumber, size_t column,
                           const union value *, int width);
 
+/* Testing. */
+struct mc_options;
+
+struct datasheet_test_params
+  {
+    /* Parameters. */
+    int max_rows;
+    int max_cols;
+    int backing_rows;
+    int backing_cols;
+
+    /* State. */
+    int next_value;
+  };
+
+struct mc_results *datasheet_test (struct mc_options *options, void *params);
+
 #endif /* data/datasheet.h */

Index: src/language/command.def
===================================================================
RCS file: /cvsroot/pspp/pspp/src/language/command.def,v
retrieving revision 1.14.2.1
retrieving revision 1.14.2.2
diff -u -b -r1.14.2.1 -r1.14.2.2
--- src/language/command.def    19 Mar 2007 21:36:24 -0000      1.14.2.1
+++ src/language/command.def    14 Apr 2007 05:04:23 -0000      1.14.2.2
@@ -127,10 +127,11 @@
 DEF_CMD (S_INPUT_PROGRAM, 0, "REREAD", cmd_reread)
 
 /* Commands for testing PSPP. */
+DEF_CMD (S_ANY, F_TESTING, "DEBUG DATASHEET", cmd_debug_datasheet)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG EVALUATE", cmd_debug_evaluate)
+DEF_CMD (S_ANY, F_TESTING, "DEBUG FLOAT FORMAT", cmd_debug_float_format)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG MOMENTS", cmd_debug_moments)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG POOL", cmd_debug_pool)
-DEF_CMD (S_ANY, F_TESTING, "DEBUG FLOAT FORMAT", cmd_debug_float_format)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG XFORM FAIL", cmd_debug_xform_fail)
 
 /* Unimplemented commands. */

Index: src/language/tests/automake.mk
===================================================================
RCS file: /cvsroot/pspp/pspp/src/language/tests/automake.mk,v
retrieving revision 1.4.2.1
retrieving revision 1.4.2.2
diff -u -b -r1.4.2.1 -r1.4.2.2
--- src/language/tests/automake.mk      19 Mar 2007 21:36:24 -0000      1.4.2.1
+++ src/language/tests/automake.mk      14 Apr 2007 05:04:23 -0000      1.4.2.2
@@ -1,6 +1,16 @@
 ## Process this file with automake to produce Makefile.in  -*- makefile -*-
 
+language_tests_built_sources = \
+       src/language/tests/check-model.c
+
 language_tests_sources = \
+       src/language/tests/datasheet-test.c \
+       src/language/tests/float-format.c \
        src/language/tests/moments-test.c \
        src/language/tests/pool-test.c \
-       src/language/tests/float-format.c
+       $(language_tests_built_sources)
+
+all_q_sources += $(language_tests_built_sources:.c=.q)
+EXTRA_DIST += $(language_tests_built_sources:.c=.q)
+CLEANFILES += $(language_tests_built_sources)
+

Index: src/libpspp/array.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/array.c,v
retrieving revision 1.10.2.1
retrieving revision 1.10.2.2
diff -u -b -r1.10.2.1 -r1.10.2.2
--- src/libpspp/array.c 19 Mar 2007 21:36:24 -0000      1.10.2.1
+++ src/libpspp/array.c 14 Apr 2007 05:04:23 -0000      1.10.2.2
@@ -390,6 +390,36 @@
     }
 }
 
+/* Moves N elements in ARRAY starting at OLD_IDX, which consists
+   of COUNT elements of SIZE bytes each, so that they now start
+   at NEW_IDX, shifting around other elements as needed. */
+void
+move_range (void *array_, size_t count, size_t size,
+            size_t old_idx, size_t new_idx, size_t n)
+{
+  assert (array_ != NULL || count == 0);
+  assert (n <= count);
+  assert (old_idx + n <= count);
+  assert (new_idx + n <= count);
+  
+  if (old_idx != new_idx && n > 0) 
+    {
+      char *array = array_;
+      char *range = xmalloc (size * n);
+      char *new = array + new_idx * size;
+      char *old = array + old_idx * size;
+
+      memcpy (range, old, size * n);
+      if (new < old)
+        memmove (new + size * n, new, (old_idx - new_idx) * size);
+      else
+        memmove (old, old + size * n, (new_idx - old_idx) * size);
+      memcpy (new, range, size * n);
+
+      free (range);
+    }
+}
+
 /* A predicate and its auxiliary data. */
 struct pred_aux 
   {

Index: src/libpspp/array.h
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/array.h,v
retrieving revision 1.6
retrieving revision 1.6.2.1
diff -u -b -r1.6 -r1.6.2.1
--- src/libpspp/array.h 29 Oct 2006 11:16:07 -0000      1.6
+++ src/libpspp/array.h 14 Apr 2007 05:04:23 -0000      1.6.2.1
@@ -115,6 +115,12 @@
 void move_element (void *array, size_t count, size_t size,
                    size_t old_idx, size_t new_idx);
 
+/* Moves N elements in ARRAY starting at OLD_IDX, which consists
+   of COUNT elements of SIZE bytes each, so that they now start
+   at NEW_IDX, shifting around other elements as needed. */
+void move_range (void *array, size_t count, size_t size,
+                 size_t old_idx, size_t new_idx, size_t n);
+
 /* Removes elements equal to ELEMENT from ARRAY, which consists
    of COUNT elements of SIZE bytes each.  Returns the number of
    remaining elements.  AUX is passed to COMPARE as auxiliary

Index: src/libpspp/automake.mk
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/automake.mk,v
retrieving revision 1.22.2.7
retrieving revision 1.22.2.8
diff -u -b -r1.22.2.7 -r1.22.2.8
--- src/libpspp/automake.mk     2 Apr 2007 13:53:49 -0000       1.22.2.7
+++ src/libpspp/automake.mk     14 Apr 2007 05:04:23 -0000      1.22.2.8
@@ -37,6 +37,8 @@
        src/libpspp/integer-format.h \
        src/libpspp/msg-locator.c \
        src/libpspp/msg-locator.h \
+       src/libpspp/model-checker.c \
+       src/libpspp/model-checker.h \
        src/libpspp/ll.c \
        src/libpspp/ll.h \
        src/libpspp/llx.c \

Index: src/libpspp/range-set.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/range-set.c,v
retrieving revision 1.1.2.2
retrieving revision 1.1.2.3
diff -u -b -r1.1.2.2 -r1.1.2.3
--- src/libpspp/range-set.c     31 Mar 2007 03:31:58 -0000      1.1.2.2
+++ src/libpspp/range-set.c     14 Apr 2007 05:04:23 -0000      1.1.2.3
@@ -100,6 +100,20 @@
   return rs;
 }
 
+/* Creates and returns a clone of OLD range set in the given POOL
+   (which may be null). */
+struct range_set *
+range_set_clone (const struct range_set *old, struct pool *pool)
+{
+  struct range_set *new;
+  struct range_set_node *node;
+
+  new = range_set_create_pool (pool);
+  for (node = first_node (old); node != NULL; node = next_node (old, node)) 
+    insert_node (new, node->start, node->end);
+  return new;
+}
+
 /* Destroys range set RS. */
 void
 range_set_destroy (struct range_set *rs) 

Index: src/libpspp/range-set.h
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/range-set.h,v
retrieving revision 1.1.2.3
retrieving revision 1.1.2.4
diff -u -b -r1.1.2.3 -r1.1.2.4
--- src/libpspp/range-set.h     31 Mar 2007 03:31:58 -0000      1.1.2.3
+++ src/libpspp/range-set.h     14 Apr 2007 05:04:23 -0000      1.1.2.4
@@ -32,6 +32,7 @@
 
 struct range_set *range_set_create (void);
 struct range_set *range_set_create_pool (struct pool *);
+struct range_set *range_set_clone (const struct range_set *, struct pool *);
 void range_set_destroy (struct range_set *);
 
 void range_set_insert (struct range_set *,

Index: src/libpspp/tower.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/tower.c,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/libpspp/tower.c 1 Apr 2007 19:32:15 -0000       1.1.2.1
+++ src/libpspp/tower.c 14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -20,13 +20,18 @@
 
 #include <libpspp/tower.h>
 
+#include <limits.h>
+
 #include <libpspp/assertion.h>
 #include <libpspp/compiler.h>
 
 static struct tower_node *abt_to_tower_node (const struct abt_node *);
 static struct tower_node *first_node (const struct tower *);
+static struct tower_node *last_node (const struct tower *);
 static struct tower_node *next_node (const struct tower *,
                                      const struct tower_node *);
+static struct tower_node *prev_node (const struct tower *,
+                                     const struct tower_node *);
 static unsigned long int get_subtree_height (const struct abt_node *);
 static void reaugment_tower_node (struct abt_node *,
                                   const struct abt_node *,
@@ -38,6 +43,7 @@
 tower_init (struct tower *t) 
 {
   abt_init (&t->abt, NULL, reaugment_tower_node, NULL);
+  t->cache_bottom = ULONG_MAX;
 }
 
 /* Returns true if T contains no nodes, false otherwise. */
@@ -64,6 +70,7 @@
   new->height = height;
   abt_insert_before (&t->abt, under ? &under->abt_node : NULL,
                      &new->abt_node);
+  t->cache_bottom = ULONG_MAX;
 }
 
 /* Deletes NODE from tower T. */
@@ -72,6 +79,7 @@
 {
   struct tower_node *next = next_node (t, node);
   abt_delete (&t->abt, &node->abt_node);
+  t->cache_bottom = ULONG_MAX;
   return next;
 }
 
@@ -83,6 +91,7 @@
   assert (new_height > 0);
   node->height = new_height;
   abt_reaugmented (&t->abt, &node->abt_node);
+  t->cache_bottom = ULONG_MAX;
 }
 
 /* Removes nodes FIRST through LAST (exclusive) from tower SRC
@@ -110,6 +119,7 @@
       abt_insert_before (&dst->abt, under ? &under->abt_node : NULL,
                          &first->abt_node);
     }
+  dst->cache_bottom = src->cache_bottom = ULONG_MAX;
 }
 
 /* Returns the node at the given HEIGHT from the bottom of tower
@@ -118,14 +128,21 @@
    of the returned node, which may be less than HEIGHT if HEIGHT
    refers to the middle of a node instead of its bottom. */
 struct tower_node *
-tower_lookup (const struct tower *t,
+tower_lookup (const struct tower *t_,
               unsigned long height,
               unsigned long *node_start) 
 {
+  struct tower *t = (struct tower *) t_;
   struct abt_node *p;
 
   assert (height < tower_height (t));
 
+  if (height >= t->cache_bottom && height - t->cache_bottom < t->cache->height)
+    {
+      *node_start = t->cache_bottom;
+      return t->cache; 
+    }
+
   *node_start = 0;
   p = t->abt.root;
   for (;;)
@@ -147,6 +164,8 @@
           if (height < node_height) 
             {
               /* Our goal height is in P. */
+              t->cache = node;
+              t->cache_bottom = *node_start;
               return node; 
             }
           else 
@@ -168,6 +187,14 @@
   return first_node (t);
 }
 
+/* Returns the node at the top of tower T, or a null pointer if T
+   is empty. */
+struct tower_node *
+tower_last (const struct tower *t) 
+{
+  return last_node (t);
+}
+
 /* If NODE is nonnull, returns the node just above NODE in tower
    T, or a null pointer if NODE is the topmost node in T.
    If NODE is null, acts like tower_first. */
@@ -177,6 +204,15 @@
   return node != NULL ? next_node (t, node) : first_node (t);
 }
 
+/* If NODE is nonnull, returns the node just below NODE in tower
+   T, or a null pointer if NODE is the bottommost node in T.
+   If NODE is null, acts like tower_last. */
+struct tower_node *
+tower_prev (const struct tower *t, const struct tower_node *node) 
+{
+  return node != NULL ? prev_node (t, node) : last_node (t);
+}
+
 /* Returns the tower node corresponding to the given ABT_NODE. */
 static struct tower_node *
 abt_to_tower_node (const struct abt_node *abt_node) 
@@ -184,20 +220,39 @@
   return abt_data (abt_node, struct tower_node, abt_node);
 }
 
+/* Returns the tower node corresponding to the given ABT_NODE. */
+static struct tower_node *
+abt_to_tower_node_null (const struct abt_node *abt_node) 
+{
+  return abt_node != NULL ? abt_to_tower_node (abt_node) : NULL;
+}
+
 /* Returns the first node in TOWER. */
 static struct tower_node *
 first_node (const struct tower *t) 
 {
-  struct abt_node *abt_node = abt_first (&t->abt);
-  return abt_node != NULL ? abt_to_tower_node (abt_node) : NULL;
+  return abt_to_tower_node_null (abt_first (&t->abt));
+}
+
+/* Returns the first node in TOWER. */
+static struct tower_node *
+last_node (const struct tower *t) 
+{
+  return abt_to_tower_node_null (abt_last (&t->abt));
 }
 
 /* Returns the next node in TOWER after NODE. */
 static struct tower_node *
 next_node (const struct tower *t, const struct tower_node *node) 
 {
-  struct abt_node *abt_node = abt_next (&t->abt, &node->abt_node);
-  return abt_node != NULL ? abt_to_tower_node (abt_node) : NULL;
+  return abt_to_tower_node_null (abt_next (&t->abt, &node->abt_node));
+}
+
+/* Returns the previous node in TOWER before NODE. */
+static struct tower_node *
+prev_node (const struct tower *t, const struct tower_node *node) 
+{
+  return abt_to_tower_node_null (abt_prev (&t->abt, &node->abt_node));
 }
 
 /* Returns the total height of the nodes in the subtree rooted at

Index: src/libpspp/tower.h
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/tower.h,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -u -b -r1.1.2.1 -r1.1.2.2
--- src/libpspp/tower.h 1 Apr 2007 19:32:15 -0000       1.1.2.1
+++ src/libpspp/tower.h 14 Apr 2007 05:04:23 -0000      1.1.2.2
@@ -75,6 +75,8 @@
 struct tower 
   {
     struct abt abt;              /* Tree. */
+    struct tower_node *cache;         /* Cache node. */
+    unsigned long int cache_bottom;   /* Height of cache's bottom. */
   };
 
 void tower_init (struct tower *);
@@ -95,7 +97,10 @@
                                  unsigned long int level,
                                  unsigned long int *node_start);
 struct tower_node *tower_first (const struct tower *);
+struct tower_node *tower_last (const struct tower *);
 struct tower_node *tower_next (const struct tower *,
                                const struct tower_node *);
+struct tower_node *tower_prev (const struct tower *,
+                               const struct tower_node *);
 
 #endif /* libpspp/tower.h */

Index: src/ui/gui/automake.mk
===================================================================
RCS file: /cvsroot/pspp/pspp/src/ui/gui/automake.mk,v
retrieving revision 1.21.2.2
retrieving revision 1.21.2.3
diff -u -b -r1.21.2.2 -r1.21.2.3
--- src/ui/gui/automake.mk      20 Mar 2007 00:08:50 -0000      1.21.2.2
+++ src/ui/gui/automake.mk      14 Apr 2007 05:04:23 -0000      1.21.2.3
@@ -1,6 +1,6 @@
 ## Process this file with automake to produce Makefile.in  -*- makefile -*-
 
-#bin_PROGRAMS += src/ui/gui/psppire
+bin_PROGRAMS += src/ui/gui/psppire
 
 src_ui_gui_psppire_CFLAGS = $(GTK_CFLAGS) $(GLADE_CFLAGS) -Wall
 

Index: src/ui/gui/psppire-case-file.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/ui/gui/psppire-case-file.c,v
retrieving revision 1.17.2.1
retrieving revision 1.17.2.2
diff -u -b -r1.17.2.1 -r1.17.2.2
--- src/ui/gui/psppire-case-file.c      19 Mar 2007 21:36:25 -0000      1.17.2.1
+++ src/ui/gui/psppire-case-file.c      14 Apr 2007 05:04:23 -0000      1.17.2.2
@@ -176,7 +176,7 @@
   g_return_val_if_fail (cf, FALSE);
   g_return_val_if_fail (cf->datasheet, FALSE);
 
-  datasheet_delete_cases (cf->datasheet, first, n_cases);
+  datasheet_delete_rows (cf->datasheet, first, n_cases);
 
   g_signal_emit (cf, signals [CASES_DELETED], 0, n_cases, first);
 
@@ -196,7 +196,7 @@
   g_return_val_if_fail (cf->datasheet, FALSE);
 
   case_clone (&tmp, cc);
-  result = datasheet_insert_cases (cf->datasheet, posn, &tmp, 1);
+  result = datasheet_insert_rows (cf->datasheet, posn, &tmp, 1);
 
   if ( result )
     g_signal_emit (cf, signals [CASE_INSERTED], 0, posn);
@@ -219,10 +219,10 @@
   g_return_val_if_fail (cf, FALSE);
   g_return_val_if_fail (cf->datasheet, FALSE);
 
-  posn = datasheet_get_case_cnt (cf->datasheet);
+  posn = datasheet_get_row_cnt (cf->datasheet);
 
   case_clone (&tmp, c);
-  result = datasheet_insert_cases (cf->datasheet, posn, &tmp, 1);
+  result = datasheet_insert_rows (cf->datasheet, posn, &tmp, 1);
 
   g_signal_emit (cf, signals [CASE_INSERTED], 0, posn);
 
@@ -238,7 +238,7 @@
   if ( ! cf->datasheet)
     return 0;
 
-  return datasheet_get_case_cnt (cf->datasheet);
+  return datasheet_get_row_cnt (cf->datasheet);
 }
 
 /* Copies the IDXth value from case CASENUM into VALUE.
@@ -254,7 +254,7 @@
   g_return_val_if_fail (cf, false);
   g_return_val_if_fail (cf->datasheet, false);
 
-  g_return_val_if_fail (idx < datasheet_get_value_cnt (cf->datasheet), false);
+  g_return_val_if_fail (idx < datasheet_get_column_cnt (cf->datasheet), false);
 
   if (value == NULL) 
     {
@@ -291,7 +291,7 @@
   g_return_val_if_fail (cf, FALSE);
   g_return_val_if_fail (cf->datasheet, FALSE);
 
-  g_return_val_if_fail (idx < datasheet_get_value_cnt (cf->datasheet), FALSE);
+  g_return_val_if_fail (idx < datasheet_get_column_cnt (cf->datasheet), FALSE);
 
   ok = datasheet_put_value (cf->datasheet, casenum, idx, v, width);
   if (ok)
@@ -313,7 +313,7 @@
   g_return_val_if_fail (cf, FALSE);
   g_return_val_if_fail (cf->datasheet, FALSE);
 
-  g_return_val_if_fail (idx < datasheet_get_value_cnt (cf->datasheet), FALSE);
+  g_return_val_if_fail (idx < datasheet_get_column_cnt (cf->datasheet), FALSE);
 
   width = fmt_var_width (fmt);
   value = xallocsa (value_cnt_from_width (width) * sizeof *value);
@@ -341,7 +341,7 @@
 
   /* FIXME: Need to have a signal to change a range of cases, instead of
      calling a signal many times */
-  for ( c = 0 ; c < datasheet_get_case_cnt (cf->datasheet) ; ++c )
+  for ( c = 0 ; c < datasheet_get_row_cnt (cf->datasheet) ; ++c )
     g_signal_emit (cf, signals [CASE_CHANGED], 0, c);
 }
 
@@ -356,10 +356,10 @@
   g_return_val_if_fail (cf, FALSE);
 
   if ( ! cf->datasheet )
-    cf->datasheet = datasheet_create_empty ();
+    cf->datasheet = datasheet_create (NULL);
 
   values = xcalloc (n_values, sizeof *values);
-  datasheet_insert_values (cf->datasheet, values, n_values, before);
+  datasheet_insert_columns (cf->datasheet, values, n_values, before);
   free (values);
 
   return TRUE;
@@ -375,5 +375,5 @@
   g_return_val_if_fail (cf, FALSE);
   g_return_val_if_fail (cf->datasheet, FALSE);
 
-  return datasheet_get_case (cf->datasheet, casenum, c);
+  return datasheet_get_row (cf->datasheet, casenum, c);
 }

Index: src/ui/gui/psppire-data-store.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/ui/gui/psppire-data-store.c,v
retrieving revision 1.33.2.1
retrieving revision 1.33.2.2
diff -u -b -r1.33.2.1 -r1.33.2.2
--- src/ui/gui/psppire-data-store.c     19 Mar 2007 21:36:25 -0000      1.33.2.1
+++ src/ui/gui/psppire-data-store.c     14 Apr 2007 05:04:23 -0000      1.33.2.2
@@ -454,7 +454,7 @@
 
 
   /* Opportunity for optimisation exists here when creating a blank case */
-  val_cnt = datasheet_get_value_cnt (ds->case_file->datasheet) ;
+  val_cnt = datasheet_get_column_cnt (ds->case_file->datasheet) ;
 
   case_create (&cc, val_cnt);
 

Index: src/ui/gui/psppire.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/ui/gui/psppire.c,v
retrieving revision 1.35.2.1
retrieving revision 1.35.2.2
diff -u -b -r1.35.2.1 -r1.35.2.2
--- src/ui/gui/psppire.c        19 Mar 2007 21:36:25 -0000      1.35.2.1
+++ src/ui/gui/psppire.c        14 Apr 2007 05:04:23 -0000      1.35.2.2
@@ -74,7 +74,7 @@
 {
   if ( NULL == s )
     psppire_case_file_replace_datasheet (the_data_store->case_file,
-                                        datasheet_create_empty ());
+                                        datasheet_create (NULL));
 }
 
 

Index: src/data/sparse-cases.c
===================================================================
RCS file: src/data/sparse-cases.c
diff -N src/data/sparse-cases.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ src/data/sparse-cases.c     14 Apr 2007 05:04:23 -0000      1.1.2.1
@@ -0,0 +1,308 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <data/sparse-cases.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <data/settings.h>
+#include <data/case-tmpfile.h>
+#include <libpspp/assertion.h>
+#include <libpspp/range-set.h>
+#include <libpspp/sparse-array.h>
+
+#include "xalloc.h"
+
+struct sparse_cases 
+  {
+    size_t column_cnt;
+    union value *default_columns;
+    casenumber max_memory_cases;
+    struct sparse_array *memory;
+    struct case_tmpfile *disk;
+    struct range_set *disk_cases;
+  };
+
+struct sparse_cases *
+sparse_cases_create (size_t column_cnt) 
+{
+  struct sparse_cases *sc = xmalloc (sizeof *sc);
+  sc->column_cnt = column_cnt;
+  sc->default_columns = NULL;
+  sc->max_memory_cases = get_workspace_cases (column_cnt);
+  sc->memory = sparse_array_create (NULL, sizeof (struct ccase));
+  sc->disk = NULL;
+  sc->disk_cases = NULL;
+  return sc;
+}
+
+struct sparse_cases *
+sparse_cases_clone (const struct sparse_cases *old) 
+{
+  struct sparse_cases *new = xmalloc (sizeof *new);
+
+  new->column_cnt = old->column_cnt;
+
+  if (old->default_columns != NULL)
+    new->default_columns
+      = xmemdup (old->default_columns,
+                 old->column_cnt * sizeof *old->default_columns);
+  else
+    new->default_columns = NULL;
+
+  new->max_memory_cases = old->max_memory_cases;
+
+  if (old->memory != NULL) 
+    {
+      unsigned long int idx;
+      struct ccase *c;
+
+      new->memory = sparse_array_create (NULL, sizeof (struct ccase));
+      for (c = sparse_array_scan (old->memory, NULL, &idx); c != NULL;
+           c = sparse_array_scan (old->memory, &idx, &idx)) 
+        case_clone (sparse_array_insert (new->memory, idx), c);
+    }
+  else
+    new->memory = NULL;
+
+  if (old->disk != NULL) 
+    {
+      const struct range_set_node *node;
+      
+      new->disk = case_tmpfile_create (old->column_cnt);
+      new->disk_cases = range_set_create ();
+      for (node = range_set_first (old->disk_cases); node != NULL;
+           node = range_set_next (old->disk_cases, node)) 
+        {
+          unsigned long int start = range_set_node_get_start (node);
+          unsigned long int end = range_set_node_get_end (node);
+          unsigned long int idx;
+          struct ccase c;
+
+          for (idx = start; idx < end; idx++) 
+            if (!case_tmpfile_get_case (old->disk, idx, &c)
+                || !case_tmpfile_put_case (new->disk, idx, &c))
+              {
+                sparse_cases_destroy (new);
+                return NULL;
+              }
+        }
+    }
+  else 
+    {
+      new->disk = NULL;
+      new->disk_cases = NULL;
+    }
+  
+  return new;
+}
+
+void
+sparse_cases_destroy (struct sparse_cases *sc) 
+{
+  if (sc != NULL) 
+    {
+      if (sc->memory != NULL) 
+        {
+          unsigned long int idx;
+          struct ccase *c;
+          for (c = sparse_array_scan (sc->memory, NULL, &idx); c != NULL;
+               c = sparse_array_scan (sc->memory, &idx, &idx)) 
+            case_destroy (c);
+          sparse_array_destroy (sc->memory);
+        }
+      free (sc->default_columns);
+      case_tmpfile_destroy (sc->disk);
+      range_set_destroy (sc->disk_cases);
+      free (sc);
+    }
+}
+
+size_t
+sparse_cases_get_value_cnt (const struct sparse_cases *sc) 
+{
+  return sc->column_cnt;
+}
+
+static bool
+dump_sparse_cases_to_disk (struct sparse_cases *sc) 
+{
+  unsigned long int idx;
+  struct ccase *c;
+
+  assert (sc->memory != NULL);
+  assert (sc->disk == NULL);
+
+  sc->disk = case_tmpfile_create (sc->column_cnt);
+  sc->disk_cases = range_set_create ();
+
+  for (c = sparse_array_scan (sc->memory, NULL, &idx); c != NULL;
+       c = sparse_array_scan (sc->memory, &idx, &idx)) 
+    {
+      if (!case_tmpfile_put_case (sc->disk, idx, c))
+        goto error;
+      range_set_insert (sc->disk_cases, idx, 1); 
+    }
+  sparse_array_destroy (sc->memory);
+  sc->memory = NULL;
+  return true;
+
+ error:
+  case_tmpfile_destroy (sc->disk);
+  sc->disk = NULL;
+  range_set_destroy (sc->disk_cases);
+  sc->disk_cases = NULL;
+  return false;
+}
+
+bool
+sparse_cases_contains_row (const struct sparse_cases *sc, casenumber row) 
+{
+  return (sc->memory != NULL
+          ? sparse_array_get (sc->memory, row) != NULL
+          : range_set_contains (sc->disk_cases, row));
+}
+
+bool
+sparse_cases_read (struct sparse_cases *sc, casenumber row, size_t column,
+                   union value *values, size_t value_cnt) 
+{
+  assert (value_cnt <= sc->column_cnt);
+  assert (column + value_cnt <= sc->column_cnt);
+
+  if (sparse_cases_contains_row (sc, row)) 
+    {
+      struct ccase c;
+      if (sc->memory != NULL) 
+        case_clone (&c, sparse_array_get (sc->memory, row));
+      else if (!case_tmpfile_get_case (sc->disk, row, &c))
+        return false;
+      case_copy_out (&c, column, values, value_cnt);
+      case_destroy (&c);
+    }
+  else 
+    {
+      assert (sc->default_columns != NULL);
+      memcpy (values, sc->default_columns + column,
+              sizeof *values * value_cnt);
+    }
+
+  return true;
+}
+
+static bool
+write_disk_case (struct sparse_cases *sc, casenumber row, size_t column,
+                 const union value *values, size_t value_cnt)
+{
+  struct ccase c;
+  bool ok;
+
+  /* Get current case data. */
+  if (column == 0 && value_cnt == sc->column_cnt) 
+    case_create (&c, sc->column_cnt); 
+  else if (!case_tmpfile_get_case (sc->disk, row, &c))
+    return false; 
+
+  /* Copy in new data. */
+  case_copy_in (&c, column, values, value_cnt);
+
+  /* Write new case. */
+  ok = case_tmpfile_put_case (sc->disk, row, &c); 
+  if (ok)
+    range_set_insert (sc->disk_cases, row, 1);
+  
+  return ok;
+}
+
+bool
+sparse_cases_write (struct sparse_cases *sc, casenumber row, size_t column,
+                    const union value *values, size_t value_cnt) 
+{
+  if (sc->memory != NULL)
+    {
+      struct ccase *c = sparse_array_get (sc->memory, row);
+      if (c == NULL) 
+        {
+          if (sparse_array_count (sc->memory) >= sc->max_memory_cases) 
+            {
+              if (!dump_sparse_cases_to_disk (sc))
+                return false;
+              return write_disk_case (sc, row, column, values, value_cnt);
+            }
+          c = sparse_array_insert (sc->memory, row);
+          
+          case_create (c, sc->column_cnt);
+          if (sc->default_columns != NULL
+              && (column != 0 || value_cnt != sc->column_cnt))
+            case_copy_in (c, 0, sc->default_columns, sc->column_cnt);
+        }
+      case_copy_in (c, column, values, value_cnt);
+      return true;
+    }
+  else
+    return write_disk_case (sc, row, column, values, value_cnt);
+}
+
+bool
+sparse_cases_write_columns (struct sparse_cases *sc, size_t start_column,
+                            const union value values[], size_t value_cnt)
+{
+  assert (value_cnt <= sc->column_cnt);
+  assert (start_column + value_cnt <= sc->column_cnt);
+
+  /* Set defaults. */
+  if (sc->default_columns == NULL)
+    sc->default_columns = xnmalloc (sc->column_cnt,
+                                    sizeof *sc->default_columns);
+  memcpy (sc->default_columns + start_column, values,
+          value_cnt * sizeof *sc->default_columns);
+
+  /* Set individual rows. */
+  if (sc->memory != NULL) 
+    {
+      struct ccase *c;
+      unsigned long int idx;
+      
+      for (c = sparse_array_scan (sc->memory, NULL, &idx); c != NULL;
+           c = sparse_array_scan (sc->memory, &idx, &idx)) 
+        case_copy_in (c, start_column, values, value_cnt);
+    }
+  else 
+    {
+      const struct range_set_node *node;
+
+      for (node = range_set_first (sc->disk_cases); node != NULL;
+           node = range_set_next (sc->disk_cases, node)) 
+        {
+          unsigned long int start = range_set_node_get_start (node);
+          unsigned long int end = range_set_node_get_end (node);
+          unsigned long int row;
+
+          for (row = start; row < end; row++) 
+            case_tmpfile_put_values (sc->disk, row,
+                                     start_column, values, value_cnt);
+        }
+              
+      if (case_tmpfile_error (sc->disk))
+        return false;
+    }
+  return true;
+}

Index: src/data/sparse-cases.h
===================================================================
RCS file: src/data/sparse-cases.h
diff -N src/data/sparse-cases.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ src/data/sparse-cases.h     14 Apr 2007 05:04:23 -0000      1.1.2.1
@@ -0,0 +1,40 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef DATA_SPARSE_CASES_H
+#define DATA_SPARSE_CASES_H 1
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <data/case.h>
+
+struct sparse_cases *sparse_cases_create (size_t value_cnt);
+struct sparse_cases *sparse_cases_clone (const struct sparse_cases *);
+void sparse_cases_destroy (struct sparse_cases *);
+
+size_t sparse_cases_get_value_cnt (const struct sparse_cases *);
+
+bool sparse_cases_contains_row (const struct sparse_cases *, casenumber row);
+bool sparse_cases_read (struct sparse_cases *, casenumber row, size_t column,
+                        union value *, size_t value_cnt);
+bool sparse_cases_write (struct sparse_cases *, casenumber row, size_t column,
+                         const union value *, size_t value_cnt);
+bool sparse_cases_write_columns (struct sparse_cases *, size_t start_column,
+                                 const union value[], size_t value_cnt);
+
+#endif /* data/sparse-cases.h */

Index: src/language/tests/check-model.h
===================================================================
RCS file: src/language/tests/check-model.h
diff -N src/language/tests/check-model.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ src/language/tests/check-model.h    14 Apr 2007 05:04:23 -0000      1.1.2.1
@@ -0,0 +1,32 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef LANGUAGE_TESTS_CHECK_MODEL
+#define LANGUAGE_TESTS_CHECK_MODEL 1
+
+#include <stdbool.h>
+
+struct lexer;
+struct mc_options;
+struct mc_results;
+
+bool check_model (struct lexer *lexer, 
+                  struct mc_results *(*checker) (struct mc_options *, void *),
+                  void *aux);
+
+#endif /* check-model.h */

Index: src/language/tests/check-model.q
===================================================================
RCS file: src/language/tests/check-model.q
diff -N src/language/tests/check-model.q
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ src/language/tests/check-model.q    14 Apr 2007 05:04:23 -0000      1.1.2.1
@@ -0,0 +1,228 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <language/tests/check-model.h>
+
+#include <errno.h>
+
+#include <libpspp/model-checker.h>
+#include <language/lexer/lexer.h>
+
+#include "error.h"
+#include "fwriteerror.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+/* (headers) */
+
+/* (specification)
+   "CHECK MODEL" (chm_):
+    search=strategy:broad/deep/random,
+           :mxd(n:max_depth),
+           :hash(n:hash_bits);
+    queue=:limit(n:queue_limit,"%s>0"),
+          drop:newest/oldest/random;
+    seed=integer;
+    stop=:states(n:max_unique_states,"%s>0"),
+         :errors(n:max_errors),
+         :timeout(d:time_limit,"%s>0");
+    progress=progress:none/dots/fancy;
+    output=:verbosity(n:verbosity),
+           :errverbosity(n:err_verbosity),
+           :file(s:output_file).
+*/
+/* (declarations) */
+/* (functions) */
+
+static struct mc_options *parse_options (struct lexer *);
+static void print_results (const struct mc_results *, FILE *);
+
+bool
+check_model (struct lexer *lexer, 
+             struct mc_results *(*checker) (struct mc_options *, void *aux),
+             void *aux)
+{
+  struct mc_options *options;
+  struct mc_results *results;
+  FILE *output_file;
+  bool ok;
+
+  options = parse_options (lexer);
+  if (options == NULL)
+    return false;
+  output_file = mc_options_get_output_file (options);
+
+  results = checker (options, aux);
+
+  print_results (results, output_file);
+
+  if (output_file != stdout && output_file != stderr) 
+    {
+      if (fwriteerror (output_file) < 0) 
+        {
+          /* We've already discarded the name of the output file.
+             Big deal. */
+          error (0, errno, "error closing output file"); 
+        }
+    }
+
+  ok = mc_results_get_error_count (results) == 0;
+  mc_results_destroy (results);
+
+  return ok;
+}
+
+static bool
+fancy_progress (struct mc *mc) 
+{
+  const struct mc_results *results = mc_get_results (mc);
+  if (mc_results_get_stop_reason (results) == MC_CONTINUING)
+    fprintf (stderr, "Processed %d unique states, max depth %d, "
+             "dropped %d duplicates...\r",
+             mc_results_get_unique_state_count (results),
+             mc_results_get_max_depth_reached (results),
+             mc_results_get_duplicate_dropped_states (results));
+  else
+    putc ('\n', stderr);
+  return true;
+}
+
+static struct mc_options *
+parse_options (struct lexer *lexer) 
+{
+  struct cmd_check_model cmd;
+  struct mc_options *options;
+
+  if (!parse_check_model (lexer, NULL, &cmd, NULL))
+    return NULL;
+
+  options = mc_options_create ();
+  if (cmd.strategy != -1)
+    mc_options_set_strategy (options,
+                             cmd.strategy == CHM_BROAD ? MC_BROAD
+                             : cmd.strategy == CHM_DEEP ? MC_DEEP
+                             : cmd.strategy == CHM_RANDOM ? MC_RANDOM
+                             : -1);
+  if (cmd.max_depth != NOT_LONG)
+    mc_options_set_max_depth (options, cmd.max_depth);
+  if (cmd.hash_bits != NOT_LONG) 
+    {
+      int hash_bits;
+      mc_options_set_hash_bits (options, cmd.hash_bits);
+      hash_bits = mc_options_get_hash_bits (options);
+      if (hash_bits != cmd.hash_bits)
+        msg (SW, _("Hash bits adjusted to %d."), hash_bits); 
+    }
+  if (cmd.queue_limit != NOT_LONG)
+    mc_options_set_queue_limit (options, cmd.queue_limit);
+  if (cmd.drop != -1) 
+    {
+      enum mc_queue_limit_strategy drop
+        = (cmd.drop == CHM_NEWEST ? MC_DROP_NEWEST
+           : cmd.drop == CHM_OLDEST ? MC_DROP_OLDEST
+           : cmd.drop == CHM_RANDOM ? MC_DROP_RANDOM
+           : -1);
+      mc_options_set_queue_limit_strategy (options, drop);
+    }
+  if (cmd.sbc_search > 0)
+    mc_options_set_seed (options, cmd.n_seed[0]);
+  if (cmd.max_unique_states != NOT_LONG)
+    mc_options_set_max_unique_states (options, cmd.max_unique_states);
+  if (cmd.max_errors != NOT_LONG)
+    mc_options_set_max_errors (options, cmd.max_errors);
+  if (cmd.time_limit != SYSMIS)
+    mc_options_set_time_limit (options, cmd.time_limit);
+  if (cmd.verbosity != NOT_LONG)
+    mc_options_set_verbosity (options, cmd.verbosity);
+  if (cmd.err_verbosity != NOT_LONG)
+    mc_options_set_failure_verbosity (options, cmd.err_verbosity);
+  if (cmd.progress != -1) 
+    {
+      if (cmd.progress == CHM_NONE)
+        mc_options_set_progress_usec (options, 0);
+      else if (cmd.progress == CHM_DOTS)
+        {
+          /* Nothing to do: that's the default anyway. */
+        }
+      else if (cmd.progress == CHM_FANCY)
+        mc_options_set_progress_func (options, fancy_progress);
+    }
+  if (cmd.output_file != NULL)
+    {
+      FILE *output_file = fopen (cmd.output_file, "w");
+      if (output_file == NULL) 
+        {
+          error (0, errno, _("error opening \"%s\" for writing"),
+                 cmd.output_file);
+          free_check_model (&cmd);
+          mc_options_destroy (options);
+          return NULL;
+        }
+      mc_options_set_output_file (options, output_file);
+    }
+
+  return options;
+}
+
+static void
+print_results (const struct mc_results *results, FILE *f) 
+{
+  enum mc_stop_reason reason = mc_results_get_stop_reason (results);
+
+  fputs ("\n"
+         "MODEL CHECKING SUMMARY\n"
+         "----------------------\n\n", f);
+
+  fprintf (f, "Stopped by: %s\n",
+           reason == MC_SUCCESS ? "state space exhaustion"
+           : reason == MC_MAX_UNIQUE_STATES ? "reaching max unique states"
+           : reason == MC_MAX_ERROR_COUNT ? "reaching max error count"
+           : reason == MC_TIMEOUT ? "reaching time limit"
+           : reason == MC_INTERRUPTED ? "user interruption"
+           : "unknown reason");
+  fprintf (f, "Errors found: %d\n\n", mc_results_get_error_count (results));
+
+  fprintf (f, "Unique states checked: %d\n",
+           mc_results_get_unique_state_count (results));
+  fprintf (f, "Maximum depth reached: %d\n",
+           mc_results_get_max_depth_reached (results));
+  fprintf (f, "Mean depth reached: %.2f\n\n",
+           mc_results_get_mean_depth_reached (results));
+
+  fprintf (f, "Dropped duplicate states: %d\n",
+           mc_results_get_duplicate_dropped_states (results));
+  fprintf (f, "Dropped off-path states: %d\n",
+           mc_results_get_off_path_dropped_states (results));
+  fprintf (f, "Dropped too-deep states: %d\n",
+           mc_results_get_depth_dropped_states (results));
+  fprintf (f, "Dropped queue-overflow states: %d\n",
+           mc_results_get_queue_dropped_states (results));
+  fprintf (f, "Checked states still queued when stopped: %d\n",
+           mc_results_get_queued_unprocessed_states (results));
+  fprintf (f, "Maximum queue length reached: %d\n\n",
+           mc_results_get_max_queue_length (results));
+
+  fprintf (f, "Runtime: %.2f seconds\n",
+           mc_results_get_duration (results));
+
+  putc ('\n', f);
+}

Index: src/language/tests/datasheet-test.c
===================================================================
RCS file: src/language/tests/datasheet-test.c
diff -N src/language/tests/datasheet-test.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ src/language/tests/datasheet-test.c 14 Apr 2007 05:04:23 -0000      1.1.2.1
@@ -0,0 +1,87 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <data/datasheet.h>
+
+#include <language/command.h>
+#include <language/lexer/lexer.h>
+#include <language/tests/check-model.h>
+#include <libpspp/array.h>
+#include <libpspp/assertion.h>
+
+#include "error.h"
+#include "xalloc.h"
+
+static bool
+parse_coordinates (struct lexer *lexer, int *rows, int *cols) 
+{
+  lex_match (lexer, '=');
+  lex_match (lexer, '(');
+
+  if (!lex_force_int (lexer))
+    return false;
+  *rows = lex_integer (lexer);
+  lex_get (lexer);
+
+  lex_match (lexer, ',');
+
+  if (!lex_force_int (lexer))
+    return false;
+  *cols = lex_integer (lexer);
+  lex_get (lexer);
+  
+  lex_match (lexer, ')');
+  return true;
+}
+
+int
+cmd_debug_datasheet (struct lexer *lexer,
+                     struct dataset *dataset UNUSED) 
+{
+  struct datasheet_test_params params;
+  bool ok;
+
+  params.max_rows = 4;
+  params.max_cols = 4;
+  params.backing_rows = 0;
+  params.backing_cols = 0;
+
+  for (;;) 
+    {
+      if (lex_match_id (lexer, "MAX"))
+        {
+          if (!parse_coordinates (lexer, &params.max_rows, &params.max_cols))
+            return CMD_FAILURE;
+        }
+      else if (lex_match_id (lexer, "BACKING"))
+        {
+          if (!parse_coordinates (lexer,
+                                  &params.backing_rows, &params.backing_cols))
+            return CMD_FAILURE;
+        }
+      else
+        break;
+      lex_match (lexer, '/');
+    }
+  
+  ok = check_model (lexer, datasheet_test, &params);
+  fprintf (stderr, "Datasheet test %s.\n", ok ? "successful" : "failed");
+  return ok ? lex_end_of_command (lexer) : CMD_FAILURE;
+}

Index: src/libpspp/model-checker.c
===================================================================
RCS file: src/libpspp/model-checker.c
diff -N src/libpspp/model-checker.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ src/libpspp/model-checker.c 14 Apr 2007 05:04:23 -0000      1.1.2.1
@@ -0,0 +1,1015 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <libpspp/model-checker.h>
+
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <libpspp/bit-vector.h>
+#include <libpspp/compiler.h>
+#include <libpspp/deque.h>
+#include <libpspp/str.h>
+#include <math/moments.h>
+
+#include "error.h"
+#include "minmax.h"
+#include "xalloc.h"
+
+/* Search options. */
+struct mc_options 
+  {
+    enum mc_strategy strategy;
+    int max_depth;
+    int hash_bits;
+    unsigned int seed;
+
+    int queue_limit;
+    enum mc_queue_limit_strategy queue_limit_strategy;
+
+    int max_unique_states;
+    int max_errors;
+    double time_limit;
+    
+    int verbosity;
+    int failure_verbosity;
+    FILE *output_file;
+
+    int progress_usec;
+    mc_progress_func *progress_func;
+
+    void *aux;
+  };
+
+static bool
+default_progress (struct mc *mc) 
+{
+  if (mc_results_get_stop_reason (mc_get_results (mc)) == MC_CONTINUING)
+    putc ('.', stderr);
+  else
+    putc ('\n', stderr);
+  return true;
+}
+
+static bool
+null_progress (struct mc *mc UNUSED) 
+{
+  return true;
+}
+
+struct mc_options *
+mc_options_create (void) 
+{
+  struct mc_options *options = xmalloc (sizeof *options);
+
+  options->strategy = MC_BROAD;
+  options->max_depth = INT_MAX;
+  options->hash_bits = 20;
+  options->seed = 0;
+  
+  options->queue_limit = 10000;
+  options->queue_limit_strategy = MC_DROP_RANDOM;
+
+  options->max_unique_states = INT_MAX;
+  options->max_errors = 1;
+  options->time_limit = 0.0;
+
+  options->verbosity = 1;
+  options->failure_verbosity = 2;
+  options->output_file = stdout;
+  options->progress_usec = 250000;
+  options->progress_func = default_progress;
+
+  options->aux = NULL;
+
+  return options;
+}
+
+struct mc_options *
+mc_options_clone (const struct mc_options *options) 
+{
+  return xmemdup (options, sizeof *options);
+}
+
+void
+mc_options_destroy (struct mc_options *options) 
+{
+  free (options);
+}
+
+enum mc_strategy
+mc_options_get_strategy (const struct mc_options *options) 
+{
+  return options->strategy;
+}
+
+void
+mc_options_set_strategy (struct mc_options *options, enum mc_strategy strategy)
+{
+  assert (strategy == MC_BROAD
+          || strategy == MC_DEEP
+          || strategy == MC_RANDOM);
+  options->strategy = strategy;
+}
+
+unsigned int
+mc_options_get_seed (const struct mc_options *options) 
+{
+  return options->seed;
+}
+
+void
+mc_options_set_seed (struct mc_options *options, unsigned int seed) 
+{
+  options->seed = seed;
+}
+
+int
+mc_options_get_max_depth (const struct mc_options *options) 
+{
+  return options->max_depth;
+}
+
+void
+mc_options_set_max_depth (struct mc_options *options, int max_depth) 
+{
+  options->max_depth = max_depth;
+}
+
+int
+mc_options_get_hash_bits (const struct mc_options *options) 
+{
+  return options->hash_bits;
+}
+
+void
+mc_options_set_hash_bits (struct mc_options *options, int hash_bits) 
+{
+  assert (hash_bits >= 0);
+  options->hash_bits = MIN (hash_bits, 31);
+}
+
+int
+mc_options_get_queue_limit (const struct mc_options *options) 
+{
+  return options->queue_limit;
+}
+
+void
+mc_options_set_queue_limit (struct mc_options *options, int queue_limit) 
+{
+  assert (queue_limit > 0);
+  options->queue_limit = queue_limit;
+}
+
+enum mc_queue_limit_strategy
+mc_options_get_queue_limit_strategy (const struct mc_options *options) 
+{
+  return options->queue_limit_strategy;
+}
+
+void
+mc_options_set_queue_limit_strategy (struct mc_options *options,
+                                     enum mc_queue_limit_strategy strategy) 
+{
+  assert (strategy == MC_DROP_NEWEST
+          || strategy == MC_DROP_OLDEST
+          || strategy == MC_DROP_RANDOM);
+  options->queue_limit_strategy = strategy;
+}
+
+int
+mc_options_get_max_unique_states (const struct mc_options *options) 
+{
+  return options->max_unique_states;
+}
+
+void
+mc_options_set_max_unique_states (struct mc_options *options,
+                                  int max_unique_states) 
+{
+  options->max_unique_states = max_unique_states;
+}
+
+int
+mc_options_get_max_errors (const struct mc_options *options) 
+{
+  return options->max_errors;
+}
+
+void
+mc_options_set_max_errors (struct mc_options *options, int max_errors) 
+{
+  options->max_errors = max_errors;
+}
+
+double
+mc_options_get_time_limit (const struct mc_options *options) 
+{
+  return options->time_limit;
+}
+
+void
+mc_options_set_time_limit (struct mc_options *options, double time_limit) 
+{
+  assert (time_limit >= 0.0);
+  options->time_limit = time_limit;
+}
+
+int
+mc_options_get_verbosity (const struct mc_options *options) 
+{
+  return options->verbosity;
+}
+
+void
+mc_options_set_verbosity (struct mc_options *options, int verbosity) 
+{
+  options->verbosity = verbosity;
+}
+
+int
+mc_options_get_failure_verbosity (const struct mc_options *options) 
+{
+  return options->failure_verbosity;
+}
+
+void
+mc_options_set_failure_verbosity (struct mc_options *options,
+                                  int failure_verbosity) 
+{
+  options->failure_verbosity = failure_verbosity;
+}
+
+FILE *
+mc_options_get_output_file (const struct mc_options *options) 
+{
+  return options->output_file;
+}
+
+void
+mc_options_set_output_file (struct mc_options *options,
+                            FILE *output_file) 
+{
+  options->output_file = output_file;
+}
+
+int
+mc_options_get_progress_usec (const struct mc_options *options) 
+{
+  return options->progress_usec;
+}
+
+void
+mc_options_set_progress_usec (struct mc_options *options, int progress_usec) 
+{
+  assert (progress_usec >= 0);
+  options->progress_usec = progress_usec;
+}
+
+mc_progress_func *
+mc_options_get_progress_func (const struct mc_options *options)
+{
+  return options->progress_func;
+}
+
+void
+mc_options_set_progress_func (struct mc_options *options,
+                              mc_progress_func *progress_func) 
+{
+  assert (options->progress_func != NULL);
+  options->progress_func = progress_func;
+}
+
+void *
+mc_options_get_aux (const struct mc_options *options) 
+{
+  return options->aux;
+}
+
+void
+mc_options_set_aux (struct mc_options *options, void *aux) 
+{
+  options->aux = aux;
+}
+
+static void
+check_options (const struct mc_options *options) 
+{
+  assert (options->queue_limit_strategy != MC_DROP_OLDEST
+          || options->strategy != MC_RANDOM);
+}
+
+struct mc_results 
+  {
+    enum mc_stop_reason stop_reason;
+    int unique_state_count;
+    int error_count;
+
+    int max_depth_reached;
+    struct moments1 *depth_moments;
+
+    int *error_path;
+    size_t error_path_len;
+
+    int duplicate_dropped_states;
+    int off_path_dropped_states;
+    int depth_dropped_states;
+    int queue_dropped_states;
+    int queued_unprocessed_states;
+    int max_queue_length;
+
+    struct timeval start;
+    struct timeval end;
+  };
+
+static struct mc_results *
+mc_results_create (void) 
+{
+  struct mc_results *results = xcalloc (1, sizeof (struct mc_results));
+  results->stop_reason = MC_CONTINUING;
+  results->depth_moments = moments1_create (MOMENT_MEAN);
+  return results;
+}
+
+void
+mc_results_destroy (struct mc_results *results) 
+{
+  if (results != NULL) 
+    {
+      moments1_destroy (results->depth_moments);
+      free (results->error_path);
+      free (results);
+    }
+}
+
+enum mc_stop_reason
+mc_results_get_stop_reason (const struct mc_results *results) 
+{
+  return results->stop_reason;
+}
+
+int
+mc_results_get_unique_state_count (const struct mc_results *results) 
+{
+  return results->unique_state_count;
+}
+
+int
+mc_results_get_error_count (const struct mc_results *results) 
+{
+  return results->error_count;
+}
+
+int
+mc_results_get_max_depth_reached (const struct mc_results *results) 
+{
+  return results->max_depth_reached;
+}
+
+double
+mc_results_get_mean_depth_reached (const struct mc_results *results) 
+{
+  double mean;
+  moments1_calculate (results->depth_moments, NULL, &mean, NULL, NULL, NULL);
+  return mean != SYSMIS ? mean : 0.0;
+}
+
+bool
+mc_results_get_last_error_path (const struct mc_results *results,
+                                const int **path, size_t *path_len) 
+{
+  if (results->error_count > 0) 
+    {
+      *path = results->error_path;
+      *path_len = results->error_path_len;
+      return true;
+    }
+  else 
+    {
+      *path = NULL;
+      *path_len = 0;
+      return false;
+    }
+}
+
+int
+mc_results_get_duplicate_dropped_states (const struct mc_results *results) 
+{
+  return results->duplicate_dropped_states;
+}
+
+int
+mc_results_get_off_path_dropped_states (const struct mc_results *results) 
+{
+  return results->off_path_dropped_states;
+}
+
+int
+mc_results_get_depth_dropped_states (const struct mc_results *results) 
+{
+  return results->depth_dropped_states;
+}
+
+int
+mc_results_get_queue_dropped_states (const struct mc_results *results) 
+{
+  return results->queue_dropped_states;
+}
+
+int
+mc_results_get_queued_unprocessed_states (const struct mc_results *results) 
+{
+  return results->queued_unprocessed_states;
+}
+
+int
+mc_results_get_max_queue_length (const struct mc_results *results) 
+{
+  return results->max_queue_length;
+}
+
+struct timeval
+mc_results_get_start (const struct mc_results *results) 
+{
+  return results->start;
+}
+
+struct timeval
+mc_results_get_end (const struct mc_results *results) 
+{
+  assert (results->stop_reason != MC_CONTINUING);
+  return results->end;
+}
+
+static double
+timeval_subtract (struct timeval x, struct timeval y) 
+{
+  /* From libc.info. */
+  double difference;
+  
+  /* Perform the carry for the later subtraction by updating Y. */
+  if (x.tv_usec < y.tv_usec) {
+    int nsec = (y.tv_usec - x.tv_usec) / 1000000 + 1;
+    y.tv_usec -= 1000000 * nsec;
+    y.tv_sec += nsec;
+  }
+  if (x.tv_usec - y.tv_usec > 1000000) {
+    int nsec = (x.tv_usec - y.tv_usec) / 1000000;
+    y.tv_usec += 1000000 * nsec;
+    y.tv_sec -= nsec;
+  }
+
+  /* Compute the time remaining to wait.
+     `tv_usec' is certainly positive. */
+  difference = (x.tv_sec - y.tv_sec) + (x.tv_usec - y.tv_usec) / 1000000.0;
+  if (x.tv_sec < y.tv_sec)
+    difference = -difference;
+  return difference;
+}
+
+double
+mc_results_get_duration (const struct mc_results *results) 
+{
+  assert (results->stop_reason != MC_CONTINUING);
+  return timeval_subtract (results->end, results->start);
+}
+
+struct mc
+  {
+    const struct mc_class *class;
+    struct mc_options *options;
+    struct mc_results *results;
+
+    unsigned char *seen_states;
+
+    struct deque state_deque;
+    struct mc_state **states;
+
+    struct mc_state *state;
+    int substate;
+
+    bool named_state;
+    bool error_state;
+
+    int include_substate;
+
+    unsigned int progress;
+    unsigned int next_progress;
+    unsigned int prev_progress;
+    struct timeval prev_progress_time;
+
+    bool interrupted;
+    bool *saved_interrupted;
+    sighandler_t saved_sigint;
+  };
+
+struct mc_state
+  {
+    int depth;
+    int *sequence;
+    void *data;
+  };
+
+static void
+free_state (const struct mc_class *class, struct mc_state *state) 
+{
+  if (state->data != NULL)
+    class->destroy (state->data);
+  free (state->sequence);
+  free (state);
+}
+
+static void
+stop (struct mc *mc, enum mc_stop_reason stop_reason) 
+{
+  if (mc->results->stop_reason == MC_CONTINUING)
+    mc->results->stop_reason = stop_reason;
+}
+
+static struct mc_state *
+make_child_state (const struct mc *mc, void *data) 
+{
+  const struct mc_state *parent = mc->state;
+  struct mc_state *new = xmalloc (sizeof *new);
+  
+  new->depth = parent->depth + 1;
+
+  new->sequence = xnmalloc (new->depth, sizeof *new->sequence);
+  memcpy (new->sequence, parent->sequence,
+          (new->depth - 1) * sizeof *parent->sequence);
+  new->sequence[new->depth - 1] = mc->substate;
+
+  new->data = data;
+
+  return new;
+}
+
+static size_t
+random_queue_index (struct mc *mc) 
+{
+  assert (!deque_is_empty (&mc->state_deque));
+  return deque_front (&mc->state_deque,
+                      rand () % deque_count (&mc->state_deque));
+}
+
+static void
+enqueue_state (struct mc *mc, struct mc_state *new) 
+{
+  size_t idx;
+
+  if (new->depth > mc->results->max_depth_reached)
+    mc->results->max_depth_reached = new->depth;
+  moments1_add (mc->results->depth_moments, new->depth, 1.0);
+
+  if (deque_count (&mc->state_deque) < mc->options->queue_limit) 
+    {
+      /* Add new state to queue. */
+      if (deque_is_full (&mc->state_deque))
+        mc->states = deque_expand (&mc->state_deque,
+                                   mc->states, sizeof *mc->states);
+      switch (mc->options->strategy) 
+        {
+        case MC_BROAD:
+          idx = deque_push_back (&mc->state_deque);
+          break;
+        case MC_DEEP:
+          idx = deque_push_front (&mc->state_deque);
+          break;
+        case MC_RANDOM:
+          if (!deque_is_empty (&mc->state_deque))
+            {
+              idx = random_queue_index (mc);
+              mc->states[deque_push_front (&mc->state_deque)]
+                = mc->states[idx];
+            }
+          else
+            idx = deque_push_front (&mc->state_deque);
+          break;
+        default:
+          NOT_REACHED ();
+        }
+      if (deque_count (&mc->state_deque) > mc->results->max_queue_length)
+        mc->results->max_queue_length = deque_count (&mc->state_deque);
+    }
+  else 
+    {
+      /* Queue has reached limit, so replace an existing
+         state. */
+      assert (!deque_is_empty (&mc->state_deque));
+      mc->results->queue_dropped_states++;
+      switch (mc->options->queue_limit_strategy) 
+        {
+        case MC_DROP_NEWEST:
+          free_state (mc->class, new);
+          return;
+        case MC_DROP_OLDEST:
+          switch (mc->options->strategy) 
+            {
+            case MC_BROAD:
+              idx = deque_front (&mc->state_deque, 0);
+              break;
+            case MC_DEEP:
+              idx = deque_back (&mc->state_deque, 0);
+              break;
+            case MC_DROP_RANDOM:
+            default:
+              NOT_REACHED ();
+            }
+          break;
+        case MC_DROP_RANDOM:
+          idx = random_queue_index (mc);
+          break;
+        default:
+          NOT_REACHED ();
+        }
+      free_state (mc->class, mc->states[idx]);
+    }
+  mc->states[idx] = new;
+}
+
+static const char *
+state_path_string (const struct mc *mc) 
+{
+  static struct string s = DS_EMPTY_INITIALIZER;
+  size_t i;
+
+  ds_clear (&s);
+  for (i = 0; i < mc->state->depth; i++) 
+    ds_put_format (&s, "%d ", mc->state->sequence[i]);
+  ds_put_format (&s, "%d", mc->substate);
+
+  return ds_cstr (&s);
+}
+
+static void
+do_error_state (struct mc *mc)
+{
+  mc->results->error_count++;
+  if (mc->results->error_count >= mc->options->max_errors)
+    stop (mc, MC_MAX_ERROR_COUNT);
+
+  if (mc->options->failure_verbosity > mc->options->verbosity)
+    {
+      struct mc_options *path_options;
+      struct mc_state *state;
+
+      fprintf (mc->options->output_file, "[%s] retracing error path:\n",
+               state_path_string (mc));
+      path_options = mc_options_clone (mc->options);
+      mc_options_set_verbosity (path_options, mc->options->failure_verbosity);
+      mc_options_set_failure_verbosity (path_options, 0);
+
+      state = make_child_state (mc, NULL);
+      
+      mc_results_destroy (mc_follow_path (mc->class, path_options,
+                                          state->sequence, state->depth));
+
+      free_state (mc->class, state);
+      putc ('\n', mc->options->output_file);
+    }
+}
+
+void
+mc_vname_state (struct mc *mc, const char *name, va_list args) 
+{
+  if (mc->named_state) 
+    fprintf (mc->options->output_file, "  [%s] warning: duplicate call "
+             "to mc_name_state (missing call to mc_add_state?)\n",
+             state_path_string (mc));
+  mc->named_state = true;
+
+  if (mc->options->verbosity > 1) 
+    {
+
+      fprintf (mc->options->output_file, "  [%s] ", state_path_string (mc));
+
+      vfprintf (mc->options->output_file, name, args);
+
+      putc ('\n', mc->options->output_file);
+    }
+}
+
+void
+mc_name_state (struct mc *mc, const char *name, ...) 
+{
+  va_list args;
+  
+  va_start (args, name);
+  mc_vname_state (mc, name, args);
+  va_end (args);
+}
+
+static void
+next_substate (struct mc *mc) 
+{
+  mc->substate++;
+  mc->named_state = false;
+
+  if (++mc->progress >= mc->next_progress) 
+    {
+      struct timeval now;
+      double elapsed, delta;
+
+      if (mc->results->stop_reason == MC_CONTINUING
+          && !mc->options->progress_func (mc))
+        stop (mc, MC_INTERRUPTED); 
+
+      gettimeofday (&now, NULL);
+
+      if (mc->options->time_limit > 0.0
+          && (timeval_subtract (now, mc->results->start)
+              > mc->options->time_limit))
+        stop (mc, MC_TIMEOUT);
+
+      elapsed = timeval_subtract (now, mc->prev_progress_time);
+      if (elapsed > 0.0)
+        {
+          /* Re-estimate the amount of progress to take
+             progress_usec microseconds. */
+          unsigned int progress = mc->progress - mc->prev_progress;
+          double progress_sec = mc->options->progress_usec / 1000000.0;
+          delta = progress / elapsed * progress_sec;
+        }
+      else 
+        {
+          /* No measurable time at all elapsed during that amount
+             of progress.  Try doubling the amount of progress
+             required. */
+          delta = (mc->progress - mc->prev_progress) * 2; 
+        }
+
+      if (delta > 0.0 && delta + mc->progress + 1.0 < UINT_MAX)
+        mc->next_progress = mc->progress + delta + 1.0;
+      else
+        mc->next_progress = mc->progress + (mc->progress - mc->prev_progress);
+
+      mc->prev_progress = mc->progress;
+      mc->prev_progress_time = now;
+    }
+}
+
+bool
+mc_discard_dup_state (struct mc *mc, unsigned int hash) 
+{
+  if (mc->options->hash_bits > 0)
+    {
+      hash &= (1u << mc->options->hash_bits) - 1;
+      if (TEST_BIT (mc->seen_states, hash)) 
+        {
+          mc->results->duplicate_dropped_states++;
+          next_substate (mc);
+          return true;
+        }
+      SET_BIT (mc->seen_states, hash);
+    }
+  return false; 
+}
+
+void
+mc_add_state (struct mc *mc, void *data) 
+{
+  if (!mc->named_state)
+    fprintf (mc->options->output_file, "  [%s] warning: unnamed state\n",
+             state_path_string (mc));
+
+  if (mc->results->stop_reason != MC_CONTINUING)
+    {
+      /* Nothing to do. */
+    }
+  else if (mc->error_state) 
+    do_error_state (mc);
+  else if (mc->include_substate >= 0
+           && mc->substate != mc->include_substate) 
+    mc->results->off_path_dropped_states++;
+  else if (mc->state->depth + 1 > mc->options->max_depth)
+    mc->results->depth_dropped_states++;
+  else 
+    {
+      /* This is the common case. */
+      mc->results->unique_state_count++;
+      if (mc->results->unique_state_count >= mc->options->max_unique_states)
+        stop (mc, MC_MAX_UNIQUE_STATES);
+      enqueue_state (mc, make_child_state (mc, data));
+      next_substate (mc);
+      return;
+    }
+
+  mc->error_state = false;
+  mc->class->destroy (data);
+  next_substate (mc);
+}
+
+static bool *interrupted = NULL;
+
+static void
+sigint_handler (int signum UNUSED) 
+{
+  *interrupted = true;
+}
+
+static void
+init_mc (struct mc *mc, const struct mc_class *class,
+         struct mc_options *options) 
+{
+  struct mc_state null_state = {0, NULL, NULL};
+
+  mc->class = class;
+  
+  mc->options = options != NULL ? options : mc_options_create ();
+  check_options (mc->options);
+
+  if (mc->options->time_limit > 0.0 && mc->options->progress_usec == 0) 
+    {
+      mc->options->progress_usec = 250000;
+      mc->options->progress_func = null_progress;
+    }
+
+  mc->results = mc_results_create ();
+  gettimeofday (&mc->results->start, NULL);
+
+  if (mc->options->hash_bits > 0) 
+    mc->seen_states = xcalloc (1, (1u << mc->options->hash_bits) / CHAR_BIT);
+  else
+    mc->seen_states = NULL;
+  mc->states = NULL;
+  deque_init_null (&mc->state_deque);
+
+  mc->substate = 0;
+  mc->state = &null_state;
+
+  mc->include_substate = -1;
+
+  mc->named_state = false;
+  mc->error_state = false;
+
+  mc->progress = 0;
+  mc->next_progress = mc->options->progress_usec != 0 ? 100 : UINT_MAX;
+  mc->prev_progress = 0;
+  mc->prev_progress_time = mc->results->start;
+
+  if (mc->options->strategy == MC_RANDOM
+      || options->queue_limit_strategy == MC_DROP_RANDOM)
+    srand (mc->options->seed);
+
+  mc->interrupted = false;
+  mc->saved_interrupted = interrupted;
+  interrupted = &mc->interrupted;
+  mc->saved_sigint = signal (SIGINT, sigint_handler);
+
+  class->init (mc);
+}
+
+static void
+finish_mc (struct mc *mc) 
+{
+  signal (SIGINT, mc->saved_sigint);
+  interrupted = mc->saved_interrupted;
+    
+  stop (mc, MC_SUCCESS);
+  gettimeofday (&mc->results->end, NULL);
+
+  mc->results->queued_unprocessed_states = deque_count (&mc->state_deque);
+  while (!deque_is_empty (&mc->state_deque))
+    {
+      struct mc_state *state = mc->states[deque_pop_front (&mc->state_deque)];
+      free_state (mc->class, state);
+    }
+
+  mc->options->progress_func (mc);
+
+  free (mc->options);
+  free (mc->states);
+  free (mc->seen_states);
+}
+
+static void
+periodic_mc (struct mc *mc) 
+{
+  if (mc->interrupted)
+    stop (mc, MC_INTERRUPTED);
+}
+
+struct mc_results *
+mc_run (const struct mc_class *class, struct mc_options *options) 
+{
+  struct mc mc;
+
+  init_mc (&mc, class, options);
+  while (!deque_is_empty (&mc.state_deque)
+         && mc.results->stop_reason == MC_CONTINUING)
+    {
+      mc.substate = 0;
+      mc.state = mc.states[deque_pop_front (&mc.state_deque)];
+      class->mutate (&mc, mc.state->data);
+      free_state (class, mc.state);
+      periodic_mc (&mc);
+    }
+  finish_mc (&mc);
+
+  return mc.results;
+}
+
+/* Should return "void *" reached by path? */
+struct mc_results *
+mc_follow_path (const struct mc_class *class, struct mc_options *options,
+                int *path, size_t path_len) 
+{
+  struct mc mc;
+  size_t i;
+
+  init_mc (&mc, class, options);
+  for (i = 1; i < path_len; i++) 
+    {
+      if (deque_is_empty (&mc.state_deque)) 
+        {
+          printf ("No state found after %zu elements in path (out of %zu)\n",
+                  i, path_len);
+          break;
+        }
+      assert (deque_count (&mc.state_deque) == 1);
+
+      mc.substate = 0;
+      mc.state = mc.states[deque_pop_front (&mc.state_deque)];
+      mc.include_substate = path[i];
+      class->mutate (&mc, mc.state->data);
+      free_state (class, mc.state);
+
+      periodic_mc (&mc);
+    }
+  finish_mc (&mc);
+
+  return mc.results;
+}
+
+void
+mc_error (struct mc *mc, const char *message, ...) 
+{
+  va_list args;
+
+  if (mc->results->stop_reason != MC_CONTINUING)
+    return;
+
+  if (mc->options->verbosity > 1)
+    fputs ("    ", mc->options->output_file);
+  fprintf (mc->options->output_file, "[%s] error: ", state_path_string (mc));
+  va_start (args, message);
+  vfprintf (mc->options->output_file, message, args);
+  va_end (args);
+  putc ('\n', mc->options->output_file);
+
+  mc->error_state = true;
+}
+
+bool
+mc_include_state (struct mc *mc) 
+{
+  if (mc->results->stop_reason != MC_CONTINUING)
+    return false;
+  if (mc->include_substate >= 0 && mc->substate != mc->include_substate) 
+    {
+      mc->substate++;
+      return false; 
+    }
+  return true;
+}
+
+const struct mc_options *
+mc_get_options (struct mc *mc) 
+{
+  return mc->options;
+}
+
+const struct mc_results *
+mc_get_results (struct mc *mc) 
+{
+  return mc->results;
+}
+
+void *
+mc_get_aux (struct mc *mc) 
+{
+  return mc_options_get_aux (mc_get_options (mc));
+}

Index: src/libpspp/model-checker.h
===================================================================
RCS file: src/libpspp/model-checker.h
diff -N src/libpspp/model-checker.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ src/libpspp/model-checker.h 14 Apr 2007 05:04:23 -0000      1.1.2.1
@@ -0,0 +1,150 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef LIBPSPP_MODEL_CHECKER_H
+#define LIBPSPP_MODEL_CHECKER_H 1
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/time.h>
+
+#include <libpspp/compiler.h>
+
+struct mc;
+
+/* Provided by each client of the model checker. */
+struct mc_class
+  {
+    void (*init) (struct mc *);
+    void (*mutate) (struct mc *, const void *);
+    void (*destroy) (void *);
+  };
+
+struct mc_options *mc_options_create (void);
+struct mc_options *mc_options_clone (const struct mc_options *);
+void mc_options_destroy (struct mc_options *);
+
+/* Search strategy. */
+enum mc_strategy 
+  {
+    MC_BROAD,           /* Breadth-first search. */
+    MC_DEEP,            /* Depth-first search. */
+    MC_RANDOM           /* Randomly ordered search. */
+  };
+
+enum mc_strategy mc_options_get_strategy (const struct mc_options *);
+void mc_options_set_strategy (struct mc_options *, enum mc_strategy);
+unsigned int mc_options_get_seed (const struct mc_options *);
+void mc_options_set_seed (struct mc_options *, unsigned int seed);
+int mc_options_get_max_depth (const struct mc_options *);
+void mc_options_set_max_depth (struct mc_options *, int max_depth);
+int mc_options_get_hash_bits (const struct mc_options *);
+void mc_options_set_hash_bits (struct mc_options *, int hash_bits);
+
+enum mc_queue_limit_strategy 
+  {
+    MC_DROP_NEWEST,
+    MC_DROP_OLDEST,
+    MC_DROP_RANDOM
+  };
+
+int mc_options_get_queue_limit (const struct mc_options *);
+void mc_options_set_queue_limit (struct mc_options *, int queue_limit);
+enum mc_queue_limit_strategy mc_options_get_queue_limit_strategy (
+  const struct mc_options *);
+void mc_options_set_queue_limit_strategy (struct mc_options *,
+                                          enum mc_queue_limit_strategy);
+
+int mc_options_get_max_unique_states (const struct mc_options *);
+void mc_options_set_max_unique_states (struct mc_options *,
+                                       int max_unique_states);
+int mc_options_get_max_errors (const struct mc_options *);
+void mc_options_set_max_errors (struct mc_options *, int max_errors);
+double mc_options_get_time_limit (const struct mc_options *);
+void mc_options_set_time_limit (struct mc_options *, double time_limit);
+
+int mc_options_get_verbosity (const struct mc_options *);
+void mc_options_set_verbosity (struct mc_options *, int verbosity);
+int mc_options_get_failure_verbosity (const struct mc_options *);
+void mc_options_set_failure_verbosity (struct mc_options *,
+                                       int failure_verbosity);
+FILE *mc_options_get_output_file (const struct mc_options *);
+void mc_options_set_output_file (struct mc_options *, FILE *);
+
+typedef bool mc_progress_func (struct mc *);
+int mc_options_get_progress_usec (const struct mc_options *);
+void mc_options_set_progress_usec (struct mc_options *, int progress_usec);
+mc_progress_func *mc_options_get_progress_func (const struct mc_options *);
+void mc_options_set_progress_func (struct mc_options *, mc_progress_func *);
+
+void *mc_options_get_aux (const struct mc_options *);
+void mc_options_set_aux (struct mc_options *, void *aux);
+
+enum mc_stop_reason 
+  {
+    MC_CONTINUING,
+    MC_SUCCESS,
+    MC_MAX_UNIQUE_STATES,
+    MC_MAX_ERROR_COUNT,
+    MC_TIMEOUT,
+    MC_INTERRUPTED
+  };
+
+struct mc_results;
+
+void mc_results_destroy (struct mc_results *);
+
+enum mc_stop_reason mc_results_get_stop_reason (const struct mc_results *);
+int mc_results_get_unique_state_count (const struct mc_results *);
+int mc_results_get_error_count (const struct mc_results *);
+
+int mc_results_get_max_depth_reached (const struct mc_results *);
+double mc_results_get_mean_depth_reached (const struct mc_results *);
+
+bool mc_results_get_last_error_path (const struct mc_results *,
+                                     const int **path, size_t *path_len);
+
+int mc_results_get_duplicate_dropped_states (const struct mc_results *);
+int mc_results_get_off_path_dropped_states (const struct mc_results *);
+int mc_results_get_depth_dropped_states (const struct mc_results *);
+int mc_results_get_queue_dropped_states (const struct mc_results *);
+int mc_results_get_queued_unprocessed_states (const struct mc_results *);
+int mc_results_get_max_queue_length (const struct mc_results *);
+
+struct timeval mc_results_get_start (const struct mc_results *);
+struct timeval mc_results_get_end (const struct mc_results *);
+double mc_results_get_duration (const struct mc_results *);
+
+struct mc_results *mc_run (const struct mc_class *, struct mc_options *);
+struct mc_results *mc_follow_path (const struct mc_class *,
+                                   struct mc_options *,
+                                   int *path, size_t path_len);
+
+bool mc_include_state (struct mc *);
+bool mc_discard_dup_state (struct mc *, unsigned int hash);
+void mc_name_state (struct mc *, const char *, ...) PRINTF_FORMAT (2, 3);
+void mc_vname_state (struct mc *, const char *, va_list) PRINTF_FORMAT (2, 0);
+void mc_error (struct mc *, const char *, ...) PRINTF_FORMAT (2, 3);
+void mc_add_state (struct mc *, void *data);
+
+const struct mc_options *mc_get_options (struct mc *);
+const struct mc_results *mc_get_results (struct mc *);
+void *mc_get_aux (struct mc *);
+
+#endif /* libpspp/model-checker.h */




reply via email to

[Prev in Thread] Current Thread [Next in Thread]