1% This file is part of the Stanford GraphBase (c) Stanford University 1993 2@i boilerplate.w %<< legal stuff: PLEASE READ IT BEFORE MAKING ANY CHANGES! 3@i gb_types.w 4 5\def\title{GB\_\,BOOKS} 6\def\<#1>{\hbox{$\langle$\rm#1$\rangle$}} 7 8\prerequisites{GB\_\,GRAPH}{GB\_\,IO} 9@* Introduction. This GraphBase module contains the |book| 10subroutine, which creates a family of undirected graphs that are based on 11classic works of literature. It also contains the |bi_book| 12subroutine, which creates a related family of bipartite graphs. 13An example of the use of |book| can be found in the demonstration 14program {\sc BOOK\_\kern.05emCOMPONENTS}. 15 16@(gb_books.h@>= 17extern Graph *book(); 18extern Graph *bi_book(); 19 20@ The subroutine call |book(@[@t\<title>@>@],n,x,first_chapter,last_chapter, 21in_weight,out_weight,seed)| 22constructs a graph based on the information in \<title>\.{.dat}, 23where \<title> is either \.{"anna"} (for {\sl Anna Karenina\/}), 24\.{"david"} (for {\sl David Copperfield\/}), 25\.{"jean"} (for {\sl Les Mis\'erables\/}), 26\.{"huck"} (for {\sl Huckleberry Finn\/}), or 27\.{"homer"} (for {\sl The Iliad\/}). 28Each vertex of the graph corresponds to one of the characters in the 29selected book. Edges between vertices correspond to encounters between 30those characters. The length of each edge is~1. 31 32Subsets of the book can be selected by specifying that the edge data should be 33restricted to chapters between |first_chapter| and |last_chapter|, 34inclusive. If |first_chapter=0|, the result is the same as if 35|first_chapter=1|. If |last_chapter=0| or if |last_chapter| exceeds 36the total number of chapters in the book, the result is the same as 37if |last_chapter| were the number of the book's final chapter. 38 39The constructed graph will have $\min(n,N)-x$ vertices, where $N$ is the 40total number of characters in the selected book. 41However, if |n| is zero, |n| is automatically made equal to the maximum 42possible value,~$N$. If |n| is less than~$N$, the |n-x| characters will be 43selected by assigning a weight to each character and choosing the |n| with 44largest weight, then excluding the largest~|x| of these, 45using random numbers to break ties in case of equal weights. 46Weights are computed by the formula 47$$ |in_weight|\cdot\\{chapters\_in}+|out_weight|\cdot\\{chapters\_out}, $$ 48where \\{chapters\_in} is the number of chapters between |first_chapter| 49and |last_chapter| in which a particular character appears, and 50\\{chapters\_out} is the number of other chapters in which that 51character appears. Both |in_weight| and |out_weight| must be at most 521,000,000 in absolute value. 53 54Vertices of the graph will appear in order of decreasing weight. 55The |seed| parameter defines the pseudo-random numbers used wherever 56a ``random'' choice between equal-weight vertices needs to be made. 57As usual with GraphBase routines, different choices of |seed| 58will in general produce different selections, 59but in a system-independent manner; identical results will be obtained on 60all computers when identical parameters have been specified. 61Any |seed| value between 0 and $2^{31}-1$ is permissible. 62 63@ Examples: The call |book("anna",0,0,0,0,0,0,0)| will construct a 64graph on 138 vertices that represent all 138 characters of Tolstoy's 65{\sl Anna Karenina\/}, as recorded in \.{anna.dat}. Two vertices will 66be adjacent if the corresponding characters 67encounter each other anywhere in the book. The call 68|book("anna",50,0,0,0,1,1,0)| is similar, but it is restricted to 69the 50 characters that occur most frequently, i.e., in the most chapters. 70The call |book("anna",50,0,10,120,1,1,0)| has the same vertices, but it 71has edges only for encounters that take place between chapter~10 72and chapter~120, inclusive. The call |book("anna",50,0,10,120,1,0,0)| is 73similar, but its vertices are the 50 characters that occur most often in 74chapters 10 through~120, without regard to how often they occur in 75the rest of the book. The call |book("anna",50,0,10,120,0,0,0)| is 76also similar, but it chooses 50 characters completely at random 77(possibly from those that don't occur in the selected chapters at all). 78 79Parameter |x|, which causes the |x| vertices of highest weight to be 80excluded, is usually either 0 or~1. It is provided primarily so that 81users can set |x=1| with respect to {\sl David Copperfield\/} and {\sl 82Huckleberry Finn}; those novels are narrated by their principal 83character, so they have edges between the principal character and 84almost everybody else. (Characters cannot get into the action of a 85first-person account unless they encounter the narrator or unless the 86narrator is quoting some other person's story.) The corresponding 87graphs tend to have more interesting connectivity properties if we 88leave the narrator out by setting |x=1|. For example, there are 87 89characters in {\sl David Copperfield\/}; the call 90|book("david",0,1,0,0,1,1,0)| produces a graph with 86 vertices, one 91for every character except David Copperfield himself. 92 93@ The subroutine call |bi_book(@[@t\<title>@>@],n,x,first_chapter,last_chapter, 94in_weight,out_weight,seed)| produces a bipartite graph in which the 95vertices of the first part are exactly the same as the vertices of the 96graph returned by |book|, while the vertices of the second part are 97the selected chapters. For example, 98$|bi_book|(|"anna"|,\allowbreak 50,0,10,120,1,1,0)$ 99creates a bipartite graph with $50+111$ vertices. There is an edge between 100each character and the chapters in which that character appears. 101 102@ Chapter numbering needs further explanation. {\sl Anna Karenina\/} 103has 239 chapters, which are numbered 1.1 through 8.19 in the 104work itself but renumbered 1 through 239 as far as the |book| routine 105is concerned. Thus, setting |first_chapter=10| and |last_chapter=120| 106turns out to be equivalent to selecting chapters 1.10 through 4.19 107(more precisely, chapter~10 of book~1 through chapter~19 of book~4). 108{\sl Les Mis\'erables\/} has an even more involved scheme; its 109356 chapters range from 1.1.1 (part~1, book~1, chapter~1) to 1105.9.6 (part~5, book~9, chapter~6). After |book| or |bi_book| has created 111a graph, the external integer variable |chapters| will contain the total 112number of chapters, and |chap_name| will be an array of strings 113containing the structured chapter numbers. For example, after 114|book("jean",@[@t\dots@>@])|, we will have |chapters=356|, 115|chap_name[1]="1.1.1"|, \dots, |chap_name[356]="5.9.6"|; 116|chap_name[0]| will be~|""|. 117 118@d MAX_CHAPS 360 /* no book will have this many chapters */ 119 120@<External variables@>= 121long chapters; /* the total number of chapters in the selected book */ 122char *chap_name[MAX_CHAPS]={""}; /* string names of those chapters */ 123 124@ As usual, we put declarations of the external variables into the header file 125for users to {\bf include}. 126 127@(gb_books.h@>= 128extern long chapters; /* the total number of chapters in the selected book */ 129extern char *chap_name[]; /* string names of those chapters */ 130 131@ If the |book| or |bi_book| routine encounters a problem, it 132returns |NULL| (\.{NULL}), 133after putting a code number into the external variable 134|panic_code|. This code number identifies the type of failure. 135Otherwise |book| returns a pointer to the newly created graph, which 136will be represented with the data structures explained in {\sc GB\_\,GRAPH}. 137(The external variable |panic_code| is itself defined in {\sc GB\_\,GRAPH}.) 138 139@d panic(c) @+{@+panic_code=c;@+gb_trouble_code=0;@+return NULL;@+} 140@# 141@f node long /* the \&{node} type is defined below */ 142 143@ The \CEE/ file \.{gb\_books.c} has the overall shape shown here. 144It makes use of an internal subroutine 145called |bgraph|, which combines the work of |book| and |bi_book|. 146 147@p 148#include "gb_io.h" /* we will use the {\sc GB\_\,IO} routines for input */ 149#include "gb_flip.h" /* we will use the {\sc GB\_\,FLIP} routines 150 for random numbers */ 151#include "gb_graph.h" /* we will use the {\sc GB\_\,GRAPH} data structures */ 152#include "gb_sort.h" /* and the |gb_linksort| routine */ 153@h@# 154@<Type declarations@>@; 155@<Private variables@>@; 156@<External variables@>@; 157@# 158static Graph *bgraph(bipartite, 159 title,n,x,first_chapter,last_chapter,in_weight,out_weight,seed) 160 long bipartite; /* should we make the graph bipartite? */ 161 char *title; /* identification of the selected book */ 162 unsigned long n; /* number of vertices desired before exclusion */ 163 unsigned long x; /* number of vertices to exclude */ 164 unsigned long first_chapter, last_chapter; 165 /* interval of chapters leading to edges */ 166 long in_weight; /* weight coefficient pertaining to chapters 167 in that interval */ 168 long out_weight; /* weight coefficient pertaining to chapters 169 not in that interval */ 170 long seed; /* random number seed */ 171{@+@<Local variables@>@;@# 172 gb_init_rand(seed); 173 @<Check that the parameters are valid@>; 174 @<Skim the data file, recording the characters and computing their 175 statistics@>; 176 @<Choose the vertices and put them into an empty graph@>; 177 @<Read the data file more carefully and fill the graph as instructed@>; 178 if (gb_trouble_code) { 179 gb_recycle(new_graph); 180 panic(alloc_fault); /* (expletive deleted) 181 we ran out of memory somewhere back there */ 182 } 183 return new_graph; 184} 185@# 186Graph *book(title,n,x,first_chapter,last_chapter,in_weight,out_weight,seed) 187 char *title; 188 unsigned long n, x, first_chapter, last_chapter; 189 long in_weight,out_weight,seed; 190{@+return bgraph(0L,title,n,x,first_chapter,last_chapter, 191 in_weight,out_weight,seed);@+} 192Graph *bi_book(title,n,x,first_chapter,last_chapter,in_weight,out_weight,seed) 193 char *title; 194 unsigned long n, x, first_chapter, last_chapter; 195 long in_weight,out_weight,seed; 196{@+return bgraph(1L,title,n,x,first_chapter,last_chapter, 197 in_weight,out_weight,seed);@+} 198 199@ @<Local var...@>= 200Graph *new_graph; /* the graph constructed by |book| or |bi_book| */ 201register long j,k; /* all-purpose indices */ 202long characters; /* the total number of characters in the selected book */ 203register node *p; /* information about the current character */ 204 205@ @d MAX_CHARS 600 /* there won't be more characters than this */ 206 207@<Check that the parameters are valid@>= 208if (n==0) n=MAX_CHARS; 209if (first_chapter==0) first_chapter=1; 210if (last_chapter==0) last_chapter=MAX_CHAPS; 211if (in_weight>1000000 || in_weight<-1000000 || 212 out_weight>1000000 || out_weight<-1000000) 213 panic(bad_specs); /* the magnitude of at least one weight is too big */ 214sprintf(file_name,"%.6s.dat",title); 215if (gb_open(file_name)!=0) 216 panic(early_data_fault); /* couldn't open the file; |io_errors| tells why */ 217 218@ @<Priv...@>= 219static char file_name[]="xxxxxx.dat"; 220 221@*Vertices. 222Each character in a book has been given a two-letter code name for 223internal use. The code names are explained at the beginning of each 224data file by a number of lines that look like this: 225$$\hbox{\tt XX \<name>,\<description>}$$ 226For example, here's one of the lines near the beginning of |"anna.dat"|: 227$$\hbox{\tt AL Alexey Alexandrovitch Karenin, minister of state}$$ 228The \<name> does not contain a comma; the \<description> might. 229 230A blank line follows the cast of characters. 231 232Internally, we will think of the two-letter code as a radix-36 integer. 233Thus \.{AA} will be the number $10\times36+10$, and \.{ZZ} will be 234$35\times36+35$. The |gb_number| routine in {\sc GB\_\,IO} is set up to 235input radix-36 integers just as it does hexadecimal ones. 236In {\sl The Iliad}, many of the minor characters have numeric digits 237in their code names because the total number of characters is too 238large to permit mnemonic codes for everybody. 239 240@d MAX_CODE 1296 /* $36\times36$, the number of two-digit codes in radix 36 */ 241 242@ In order to choose the vertices, we want to represent each character 243as a node whose key corresponds to its weight; then the |gb_linksort| 244routine of {\sc GB\_\,SORT} will provide the desired rank-ordering. We will 245find it convenient to use these nodes for all the data processing that 246|bgraph| has to do. 247 248@<Type dec...@>= 249typedef struct node_struct { /* records to be sorted by |gb_linksort| */ 250 long key; /* the nonnegative sort key (weight plus $2^{30}$) */ 251 struct node_struct *link; /* pointer to next record */ 252 long code; /* code number of this character */ 253 long in; /* number of occurrences in selected chapters */ 254 long out; /* number of occurrences in unselected chapters */ 255 long chap; /* seen most recently in this chapter */ 256 Vertex *vert; /* vertex corresponding to this character */ 257} node; 258 259@ Not only do nodes point to codes, we also want codes to point to nodes. 260 261@<Priv...@>= 262static node node_block[MAX_CHARS]; /* array of nodes for working storage */ 263static node *xnode[MAX_CODE]; /* the node, if any, having a given code */ 264 265@ We will read the data file twice, once quickly (to collect statistics) 266and once more thoroughly (to record detailed information). Here is the 267quick version. 268 269@<Skim the data file, recording the characters...@>= 270@<Read the character codes at the beginning of the data file, and 271 prepare a node for each one@>; 272@<Skim the chapter information, counting the number of chapters in 273 which each character appears@>; 274if (gb_close()!=0) 275 panic(late_data_fault); 276 /* checksum or other failure in data file; see |io_errors| */ 277 278@ @<Read the character codes...@>= 279for (k=0;k<MAX_CODE;k++) xnode[k]=NULL; 280{@+register long c; /* current code entering the system */ 281 p=node_block; /* current node entering the system */ 282 while ((c=gb_number(36))!=0) { /* note that \.{00} is not a legal code */ 283 if (c>=MAX_CODE || gb_char()!=' ') panic(syntax_error); 284 /* unreadable line in data file */ 285 if (p>=&node_block[MAX_CHARS]) 286 panic(syntax_error+1); /* data has too many characters */ 287 p->link=(p==node_block?NULL:p-1); 288 p->code=c; 289 xnode[c]=p; 290 p->in=p->out=p->chap=0; 291 p->vert=NULL; 292 p++; 293 gb_newline(); 294 } 295 characters=p-node_block; 296 gb_newline(); /* bypass the blank line that terminates the character data */ 297} 298 299@ Later we will read through this part of the file again, extracting 300additional information if it turns out to be relevant. The 301\<description> string is provided to users in a |desc| field, 302in case anybody cares to look at it. The |in| and |out| statistics 303are also made available in utility fields called |in_count| and |out_count|. 304The code value is placed in the |short_code| field. 305 306@d desc z.S /* utility field |z| points to the \<description> string */ 307@d in_count y.I /* utility field |y| counts appearances in selected chapters */ 308@d out_count x.I /* utility field |x| counts appearances in other chapters */ 309@d short_code u.I /* utility field |u| contains a radix-36 number */ 310 311@<Read the data about characters again, noting vertex names and the 312 associated descriptions@>= 313{@+register long c; /* current code entering the system a second time */ 314 while ((c=gb_number(36))!=0) {@+register Vertex *v=xnode[c]->vert; 315 if (v) { 316 if (gb_char()!=' ') panic(impossible); /* can't happen */ 317 gb_string(str_buf,','); /* scan the \<name> part */ 318 v->name=gb_save_string(str_buf); 319 if (gb_char()!=',') 320 panic(syntax_error+2); /* missing comma after \<name> */ 321 if (gb_char()!=' ') 322 panic(syntax_error+3); /* missing space after comma */ 323 gb_string(str_buf,'\n'); /* scan the \<description> part */ 324 v->desc=gb_save_string(str_buf); 325 v->in_count=xnode[c]->in; 326 v->out_count=xnode[c]->out; 327 v->short_code=c; 328 } 329 gb_newline(); 330 } 331 gb_newline(); /* bypass the blank line that terminates the character data */ 332} 333 334@ @(gb_books.h@>= 335#define desc @t\quad@> z.S /* utility field definitions for the header file */ 336#define in_count @t\quad@> y.I 337#define out_count @t\quad@> x.I 338#define short_code @t\quad@> u.I 339 340@*Edges. 341The second part of the data file has a line for each chapter, containing 342``cliques of encounters.'' For example, the line 343$$\hbox{\tt3.22:AA,BB,CC,DD;CC,DD,EE;AA,FF}$$ 344means that, in chapter 22 of book 3, there were encounters between the pairs 345$$\def\\{{\rm,} } 346\hbox{\tt AA-BB\\AA-CC\\AA-DD\\BB-CC\\BB-DD\\CC-DD\\CC-EE\\DD-EE\\{\rm and }% 347AA-FF\rm.}$$ 348(The encounter \.{CC-DD} is specified twice, once in the clique 349\.{AA,BB,CC,DD} and once in \.{CC,DD,EE}; this does not imply anything about 350the actual number of encounters between \.{CC} and \.{DD} in the chapter.) 351 352A clique might involve one character only, when that character is featured 353in sort of a soliloquy. 354 355A chapter might contain no references to characters at all. In such a case 356the `\.:' following the chapter number is omitted. 357 358There might be more encounters than will fit on a single line. In such cases, 359continuation lines begin with `\.{\&:}'. This convention turns out to be 360needed only in \.{homer.dat}; chapters in {\sl The Iliad\/} are 361substantially more complex than the chapters in other GraphBase books. 362 363On our first pass over the data, we simply want to compute statistics about 364who appears in what chapters, so we ignore the distinction between 365commas and semicolons. 366 367@<Skim the chapter information, counting the number of chapters in 368 which each character appears@>= 369for (k=1; k<MAX_CHAPS && !gb_eof(); k++) { 370 gb_string(str_buf,':'); /* read past the chapter number */ 371 if (str_buf[0]=='&') k--; /* continuation of previous chapter */ 372 while (gb_char()!='\n') {@+register long c=gb_number(36); 373 if (c>=MAX_CODE) 374 panic(syntax_error+4); /* missing punctuation between characters */ 375 p=xnode[c]; 376 if (p==NULL) panic(syntax_error+5); /* unknown character */ 377 if (p->chap!=k) { 378 p->chap=k; 379 if (k>=first_chapter && k<=last_chapter) p->in++; 380 else p->out++; 381 } 382 } 383 gb_newline(); 384} 385if (k==MAX_CHAPS) panic(syntax_error+6); /* too many chapters */ 386chapters=k-1; 387 388@ Our second pass over the data is very similar to the first, if we 389are simply computing a bipartite graph. In that case we add an edge 390to the graph between each selected chapter and each selected character 391in that chapter. Local variable |chap_base| will point to a 392vertex such that |chap_base+k| is the vertex corresponding to chapter~|k|. 393 394The |in_count| of a chapter vertex is the degree of that vertex, i.e., the 395number of selected characters that appear in the corresponding chapter. 396The |out_count| is the number of characters that appear in the 397chapter but were omitted from the graph. Thus the |in_count| and 398|out_count| for chapters are analogous to the |in_count| and |out_count| 399for characters. 400 401@<Read the chapter information a second time and create the 402 appropriate bipartite edges@>= 403{ 404 for (p=node_block;p<node_block+characters;p++) p->chap=0; 405 for (k=1; !gb_eof(); k++) { 406 gb_string(str_buf,':'); /* read the chapter number */ 407 if (str_buf[0]=='&') k--; 408 else { 409 if (str_buf[strlen(str_buf)-1]=='\n') str_buf[strlen(str_buf)-1]='\0'; 410 chap_name[k]=gb_save_string(str_buf); 411 } 412 if (k>=first_chapter && k<=last_chapter) {@+register Vertex *u=chap_base+k; 413 if (str_buf[0]!='&') { 414 u->name=chap_name[k]; 415 u->desc=null_string; 416 u->in_count=u->out_count=0; 417 } 418 while (gb_char()!='\n') {@+register long c=gb_number(36); 419 p=xnode[c]; 420 if (p->chap!=k) {@+register Vertex *v=p->vert; 421 p->chap=k; 422 if (v) {@+ 423 gb_new_edge(v,u,1L); 424 u->in_count++; 425 }@+else u->out_count++; 426 } 427 } 428 } 429 gb_newline(); 430 } 431} 432 433@ @<Local variables@>= 434Vertex *chap_base; 435 /* the bipartite vertex for chapter~|k| is |chap_base+k| */ 436 437@ The second pass has to work a little harder when we are recording 438encounters from cliques, but the logic isn't difficult really. 439We insert a reference to the first chapter that generated each edge, in 440utility field |chap_no| of the corresponding |Arc| record. 441 442@d chap_no a.I /* utility field |a| holds a chapter number */ 443 444@<Read the chapter information a second time and create the 445 appropriate edges for encounters@>= 446for (k=1; !gb_eof(); k++) {@+char *s; 447 s=gb_string(str_buf,':'); /* read the chapter number */ 448 if (str_buf[0]=='&') k--; 449 else {@+if (*(s-2)=='\n') *(s-2)='\0'; 450 chap_name[k]=gb_save_string(str_buf); 451 } 452 if (k>=first_chapter && k<=last_chapter) {@+register long c=gb_char(); 453 while (c!='\n') {@+register Vertex **pp=clique_table; 454 register Vertex **qq,**rr; /* pointers within the clique table */ 455 do@+{@+ 456 c=gb_number(36); /* set |c| to code for next character of clique */ 457 if (xnode[c]->vert) /* is that character a selected vertex? */ 458 *pp++=xnode[c]->vert; 459 /* if so, that vertex joins the current clique */ 460 c=gb_char(); 461 }@+while (c==','); /* repeat until end of the clique */ 462 for (qq=clique_table;qq+1<pp;qq++) 463 for (rr=qq+1;rr<pp;rr++) 464 @<Make the vertices |*qq| and |*rr| adjacent, 465 if they aren't already@>; 466 } 467 } 468 gb_newline(); 469} 470 471@ @(gb_books.h@>= 472#define chap_no @[a.I@] /* utility field definition in the header file */ 473 474@ @<Priv...@>= 475static Vertex *clique_table[30]; 476 /* pointers to vertices in the current clique */ 477 478@ @<Make the vertices |*qq| and |*rr| adjacent...@>= 479{@+register Vertex *u=*qq, *v=*rr; 480 register Arc *a; 481 for (a=u->arcs; a; a=a->next) 482 if (a->tip==v) goto found; 483 gb_new_edge(u,v,1L); /* not found, so they weren't already adjacent */ 484 if (u<v) a=u->arcs; 485 else a=v->arcs; /* the new edge consists of arcs |a| and |a+1| */ 486 a->chap_no=(a+1)->chap_no=k; 487found:; 488} 489 490@*Administration. 491The program is now complete except for a few missing organizational details. 492I will add these after lunch. 493@^out to lunch@> 494 495@ OK, I'm back; what needs to be done? The main thing is to create 496the graph itself. 497 498@<Choose the vertices and put them into an empty graph@>= 499if (n>characters) n=characters; 500if (x>n) x=n; 501if (last_chapter>chapters) last_chapter=chapters; 502if (first_chapter>last_chapter) first_chapter=last_chapter+1; 503new_graph=gb_new_graph(n-x+(bipartite?last_chapter-first_chapter+1:0)); 504if (new_graph==NULL) panic(no_room); /* out of memory already */ 505strcpy(new_graph->util_types,"IZZIISIZZZZZZZ"); 506 /* declare the types of utility fields */ 507sprintf(new_graph->id,"%sbook(\"%s\",%lu,%lu,%lu,%lu,%ld,%ld,%ld)", 508 bipartite?"bi_":"",title,n,x,first_chapter,last_chapter, 509 in_weight,out_weight,seed); 510if (bipartite) { 511 mark_bipartite(new_graph,n-x); 512 chap_base=new_graph->vertices+(new_graph->n_1-first_chapter); 513} 514@<Compute the weights and assign vertices to chosen nodes@>; 515 516@ @<Compute the weights and assign vertices to chosen nodes@>= 517for (p=node_block; p<node_block+characters; p++) 518 p->key=in_weight*(p->in)+out_weight*(p->out)+0x40000000; 519gb_linksort(node_block+characters-1); 520k=n; /* we will look at this many nodes */ 521{@+register Vertex *v=new_graph->vertices; /* the next vertex to define */ 522 for (j=127; j>=0; j--) 523 for (p=(node*)gb_sorted[j]; p; p=p->link) { 524 if (x>0) x--; /* ignore this node */ 525 else p->vert=v++; /* choose this node */ 526 if (--k==0) goto done; 527 } 528} 529done:; 530 531@ Once the graph is there, we're ready to fill it in. 532 533@<Read the data file more carefully and fill the graph as instructed@>= 534if (gb_open(file_name)!=0) 535 panic(impossible+1); 536 /* this can't happen, because we were successful before */ 537@<Read the data about characters again, noting vertex names and the 538 associated descriptions@>; 539if (bipartite) 540 @<Read the chapter information a second time and create the 541 appropriate bipartite edges@>@; 542else @<Read the chapter information a second time and create the 543 appropriate edges for encounters@>; 544if (gb_close()!=0) 545 panic(impossible+2); /* again, can hardly happen the second time around */ 546 547@* Index. As usual, we close with an index that 548shows where the identifiers of \\{gb\_books} are defined and used. 549