Major Processes in SILKin

This section of the documentation describes all major processes in SILKin, cross-referenced with the code documentation. Because the code documentation is generated by Java, and the only internal named points in the HTML files are for the FIELDS SUMMARY and the METHODS SUMMARY of each class, I will link to one of those or to the class in general. This section generally follows the order of menu choices in the top menu bar of the SIL_Edit screen (File, Edit, etc.).

When referring to the chain of method calls that produces the action described in any operation, I will use the  '->'  symbol for a method call.
EXAMPLE: ClassName:top-level-method -> methodA -> methodB -> final-method.
This will be called the 'call stack' for a particular operation. If no new ClassName is given, the method is in the same class as the calling method.

File Operations

File > New

This has a sub-menu offering a new context or a new Library Browser. The Library Browser option is only offered in Admin mode.

call stack: ChartPanel: personMenu_ListSelect -> getProjectName

File > Open

This functions in the standard way, using the host Operating System's file open tools. It will only allow selection of a SILK file. A parser is launched to read in the file.

File > Open call stack: SIL_Edit:loadItemActionPerformed  ->   ChartPanel:pickSILKFile  ->   [standard FileChooser operations]  ->   loadSILKFile  ->   Library:loadSILKFile  ->   Linus (constructor)  ->   Tokenizer (constructor)  ->   ParserSILKFile:parseSILKFile  ->   parseHeader()  ->   parseBody() etc.  ->   Library:retrieveOrCreateStub  ->   Context:rebuildLinkMethods  ->   SIL_Edit:buildLinkMethods  ->   synchronizeLabelParams  ->   setSnap  ->   PersonPanel:setDistinctAdrTerms  ->   SIL_Edit:setTitle  ->   rebuildRecentSubMenu  ->   PersonPanel:rebuildEgoBox  ->   initUDPCombo  ->   ChartPanel:checkSizeOfChart  ->   resizeAndRepaint

File > Open Recent

This is similar to most apps. A list of the five most-recently opened SILK files is stored on the Contexts.stub file and loaded into Library:recentFiles. Any new file opened with the straight 'Open' command is added to the top of the list.

Prior Versions & Automatic Saves

In version 2.4, we added some mostly invisible file back-ups. Our goal is to minimize the amount of data lost in a crash or file corruption. If the User is working on Adam-and-Eve.silk in the Context Under Construction folder, that continues to be the file shown in 'Open Recent'. But we have added the following processes to the Open and Save operations:

File Open Exception handling call stack: Context:craftBackupMsg  ->   revertToPrior  ->   loadSILKFile [a recursive call]

File > GEDCOM

The submenu under this command allows for import or export of files in GEDCOM format. GEDCOM (GEnealogy Data COMunication standard) is not the product of any standards body; it was developed by the Mormon church's genealogy repository so that Mormons wishing to document their heritage could use whatever software they wanted to build their family trees and then submit them in a 'standard' format. It is not very standard. It is an XML format that allows for many different ways to record the same relationship. I have tried to accomodate the 5.5 release, but there is no guarantee that a file generated by someone else's app will read in correctly to SILKin, and vice versa.

The GEDCOM parsing methods use the GEDCOM_DFA file in the Library.

Import call stack: SIL_Edit:importGEDCOMItemActionPerformed  ->   MainPane:importGEDCOM  ->   ParserGEDCOM:parseFile.
Export call stack: SIL_Edit:exportGEDCOMItemActionPerformed  ->   MainPane:exportGEDCOM  ->   Context:exportGEDCOM.

File > Save and Save As...

These work in the normal way. If the User chooses to save a SILK file in a folder other than the Library folder's Context Under Construction folder, we record the filepath to this folder and store it as the User's 'edit directory' in the Library and the current Context.

Save call stack: SIL_Edit:saveItemActionPerformed  ->   ChartPanel:saveSILKFile  ->   Context:writeSILKFile  ->   makePriorVersion  ->   getPriors  ->   FilePicker:accept.

Save As call stack: SIL_Edit:saveAsItemActionPerformed  ->  ChartPanel:saveAsFile  ->  saveSILKFile  ->  Context:writeSILKFile  ->   makePriorVersion  ->   getPriors  ->   FilePicker:accept.

File Save (or Save As) Exception handling call stack: Context:craftBackupMsg  ->   revertToPrior  ->   System.exit(7)

File > Clear All

This command removes the current Context from all screens and memory. This is to be used when a new context is about to be entered. Some Users may invoke this when they want to load (File  -> Open) a new context, but that is unnecessary.

call stack: SIL_Edit:deleteAllItemActionPerformed  ->  ChartPanel:deleteAll

File > Print

The submenu of this command gives 3 options:

  1. Print Visible Chart Portion
  2. Print Current Chart
  3. Print All Charts
Whatever is chosen to be printed will be sent to the local operating system's print queue. Because printing fonts are different from screen fonts, these routines use the font stored as PrintChart:printFont. It can be changed via Edit > Edit Prefs. Note that I use only the basic Java printing methods. User does not have control of scaling, or other nice features.

Printing call stack: SIL_Edit:printVisibleItemActionPerformed  ->  PrintChart:PrintChart (constructor)  ->  printTheChart

NOTE: all 3 menu choices have the same call stack except for the ..ItemActionPerformed method, which sets a parameter and iterates over all charts for the third option.

File > Page Setup

This calls up a PageSetup dialog box (OS dependent) allowing User to to choose landscape vs. portrait mode. The choice is stored in PrintChart:pgFormat.

call stack: SIL_Edit:pageSetupItemActionPerformed

File > Exit

This command checks for any unsaved changes to the current context. If any are found, User is asked if she wants to save them. Then the Java exit method is called.

call stack: SIL_Edit:quitItemActionPerformed  ->  System:exit.

Entering Kinship Data

There is no menu choice for entering data; that is the expected action whenever the ChartPanel is displayed. This is what the User normally does after invoking the File menu to either create a new context or open one in progress.

  Chart Window Showing an Individual Detail Display

This window was created originally using the GUI Generator in NetBeans. Later, when the constraints of the generator became irksome, I captured all the generated code and made my own ChartWindow.java file that I could control directly. Many other windows in SILKin were created with the generator and kept that way. When looking at the source code files, any XYZ.java file that has an accompanying XYZ.form file is using generated code. When viewing the XYZ.java file in the NetBeans IDE window, there will be buttons at the top that allow you to choose between viewing the source code or viewing the GUI form. The layout of the window can be changed by modifying the GUI form.

Unless otherwise noted, all windows discussed below have generated code.

Creating People and Families on the Chart

User will click on the chart in the location where she wants to add the symbol for a person, family, or link. If this is the first symbol for a brand new empty context, SILKin will present a pop-up asking User to name the project. Otherwise a pop-up menu appears allowing the creation of 5 kinds of symbols:

When a person or link is created, the current chart becomes their Individual:homeChart. For a Link, a pop-up menu of all persons in the current context is displayed; User must choose the person that this link will point to. After any change to the chart, the ChartPanel:repaint method is called.

Male or Female call stack: ChartPanel: personMenu_ListSelect  ->  Individual:Individual (constructor)  ->  PersonPanel:addToEgoChoices.
Union call stack: ChartPanel: personMenu_ListSelect  ->  Family:Family (constructor).
Link call stack: ChartPanel: personMenu_ListSelect  ->  Link:Link (constructor).

Clicking on 'Draw Special Relationship' is more complex. If no chartable UDPs have been created in this context, an error pop-up is shown. If more than one chartable UDP exists, a pop-up menu of them is presented so User can choose the correct one. Each one has a User-selected unique color for the connecting line.

Once a valid UDP is identified, flags are set that indicate a Special Relationship is in progress. User is asked to click on the 'parent' or person to whom this UDP instance is attached. (Chartable UDPs will almost always be used to chart adoptions, so parent-child nomenclature is used. But technically, this could be any valid non-genealogical bilateral relationship.) When the KinshipEditor_MouseDown method runs and detects a Special Relationship is in progress, it stores information on the symbol clicked. The captureAdoption method uses this data for the parent role and calls createChartableUDP_2. Then User is asked to click on the 'child', mouseDown again stores the data, and captureAdoption calls createChartableUDP_3. The flags are cleared, the special colored line is created from parent to child and the screen redrawn.

Special Relationship (UDP) call stack: ChartPanel: personMenu_ListSelect  ->  createChartableUDP_1  ->  KinshipEditor_MouseDown  ->  captureAdoption  ->  createChartableUDP_2  ->  KinshipEditor_MouseDown  ->  captureAdoption  ->  createChartableUDP_3  ->  cancelAdoption.

Connecting People via Families

In SILKin the only connection that is allowed to go directly from one person to another person is a Special Relationship — and usually those link an adopted child to a family (i.e. both parents) rather than just one person. All genealogical connections between people are done through a Family (Union).

By convention, a husband and wife are drawn with their Union symbol between them, but SILKin does not enforce that convention (and it breaks down in polygamous situations anyway). If User has just placed Adam on a chart, she would normally then place a Union next to Adam and place Eve on the other side of the Union. Making them adjacent does not create a connection. User then must grab Adam with the mouse and drag him to the top of the Union symbol. When a person is held on top of a Union, a green circle appears to let the User know she has connected. If User releases Adam while the green circle is showing, Adam is made the husband in this Union (unless there already is one – then an error pop-up reports there can only be one husband in a Union). Adam pops back to his position before the drag-and-drop, and a line is drawn from the bottom of Adam to the bottom center of the Union. Ditto for Eve. Dropping two parents of the same sex also generates an error.

Add Parent call stack: ChartPanel: KinshipEditor_MouseDrag  ->  KinshipEditor_MouseUp  ->  addOrDeleteSpouse  ->  Marriage:eligibleSpouse  ->  isSib  ->  delSib  ->  addSpouse  ->  Family:addParent.

The procedure for adding a person to a Union as a child is identical, except User will drag-and-drop them to the bottom of the Union symbol. A line will be drawn from the bottom center of the Union symbol to the child. There is no limit on the number of children in a Family (Union), but a person can only be added once, and no parent can be added as a child.

It is desirable that twins, triplets, etc. be indicated on the chart with diagonal lines dropping from the Union symbol. To do this, we must know when a child belongs to a multiple-birth group. So all children in a Family are members of a Marriage.BirthGroup. BirthGroups with only one member get the usual vertical lines of descent on the chart. BirthGroups with multiple members are fanned out and get diagonal lines of descent.

Add Child call stack: ChartPanel: KinshipEditor_MouseDrag  ->  KinshipEditor_MouseUp  ->  addChild  ->  Family:computeBirthGrps  ->  addChild  ->  Marriage:addSib

SILKin allows the User to draw the persons in a context on as many chart pages as they want. Some may want each nuclear family on their own page, or each village, or some other scheme. This requires some mechanism for linking a person on Chart A to a family on some other chart page. A Link supplies this capability.

Assume that Abram on Chart B marries Sarah on Chart F. User may want to draw that marriage on Chart B or F. Or she may choose to create a new chart page for this new family. In either case, after creating the Union symbol on the chart of her choice she will create one or two Link objects and drag-and-drop them on the top of the Union symbol. Dropping a link to Sarah on a Union has exactly the same effect as dropping the Individual Sarah.

Add Spouse Link call stack: ChartPanel: KinshipEditor_MouseDrag  ->  KinshipEditor_MouseUp  ->  addOrDeleteSpouseLink  ->  addOrDeleteSpouse  ->  Marriage:isSpouse  ->  eligibleSpouse  ->  isSib  ->  Family:deleteChild  ->  Marriage:delSib  ->  Family:computeBirthGrps.

Dropping a Link on the bottom of a Family adds a child in a similar manner.

Add Child Link call stack: ChartPanel: KinshipEditor_MouseDrag  ->  KinshipEditor_MouseUp  ->  addChildLink  ->  addChild  ->  Marriage:isSpouse  ->  addSib  ->  Family:addChild  ->  computeBirthGrps.

Deleting People from Families

In KAES, the method for deleting someone from a family was the same as for adding them: drag-and-drop the person onto the family symbol. Although that is not intuitively obvious, it was already programmed and I couldn't think of a different way that was more intuitive. So in SILKin you drag-and-drop to add someone to a family, and do it a second time to delete them. It is a 'toggle' operation.

Deleting parents is more complex than adding them, because they may have played a role in a Special Relationship while in the family. The test for that condition uses Context.SpecRelTriple instances. Special Relationships (i.e. adoptions) create a connection between an adopted child and either an individual parent or a family. In the former case, we can just delete the parent from this family, because the adoption link stays attached to the individual parent. But if the link is to this family, that link must be transferred to this parent and the other spouse as individuals. SILKin assumes that a marriage may be terminated without breaking the adoption bonds.

Delete Parent call stack: ChartPanel: KinshipEditor_MouseDrag  ->  KinshipEditor_MouseUp  ->  addOrDeleteSpouse  ->  Family:deleteSpouse  ->  Marriage:delSpouse  ->  ChartPanel:removePersonAndRecomputeNodes  ->  Family:computeBirthGrps.

Deleting a (natural) child is straightforward.

Delete Child call stack: ChartPanel: KinshipEditor_MouseDrag  ->  KinshipEditor_MouseUp  ->  Marriage:isSib  ->  ChartPanel:removeChild  ->  Family:deleteChild  ->  Marriage:delSib.

Deleting a Special Relationship (adoption) is somewhat similar. Because adding and deleting people to/from a Union is a 'toggle' operation, I made deleting a Special Relationship also a toggle. If the current chart shows that Mother Jones has an adopted child Orphan Annie, the way to delete that adoptive relationship is to again choose Draw Special Relationship and designate Mother Jones as the parent and Orphan Annie as the child. This will make the special-colored line disappear between the two persons.

The Delete Special Relationship call stack is virtually identical to the one for Special Relationship (UDP).

Editing Family Tree Charts

Again, there is no menu choice for editing a chart. I believe it is fairly intuitive in a GUI that if you don't like the position of a symbol, you drag-and-drop it to a different location. In case that is not intuitive, the Help system gives explicit instructions.

Moving a person is just that simple; drag them and drop them. The only wrinkle is that if 'Snap to Grid' is on (the default), a very small drag will not change the position of the symbol, for obvious reasons.

Move Person call stack: ChartPanel: KinshipEditor_MouseDrag  ->  Person:folks  ->  setLocation  ->  ChartPanel:delayedAreaCk

Moving a family (union) has a few options. If you hold down the Shift Key while dragging a family symbol, you are moving the entire nuclear family: parents and children, but not children's spouses. If you hold down the Alt Key (on a PC) or the Command Key (on a Mac), you are moving the family and its entire lineage: children, grandchildren, great grandchildren, etc. including the spouses of children, grandchildren, etc.

Move Family (no modifier keys) call stack: ChartPanel: KinshipEditor_MouseDrag  ->  Marriage:knots  ->  setLocationX  ->  setLocationY  ->  ChartPanel:delayedAreaCk

Move Family (Shift Key) call stack: ChartPanel: KinshipEditor_MouseDrag  ->  Marriage:knots  ->  deltaMove  ->  ChartPanel:delayedAreaCk

Move Family (Alt/Command Key) call stack: ChartPanel: KinshipEditor_MouseDrag  ->  Marriage:knots  ->  lineageDeltaMove  ->  ChartPanel:delayedAreaCk

The delayedAreaCk method updates the list of all people and families that have been moved or created. The next time we check to see if all symbols are within the chart's boundaries, this list contains the only folks we aren't sure about. If a symbol has been moved (or created) outside (or too close to) the current bounds, the chart's area will be enlarged.

Repainting the Chart Screen

Any operation that changes the appearance of the chart will be followed with a call to the repaint method. This is an expansion and adaptation of Mike Fischer's original AWT code, so it is a messy combination of AWT and Swing graphics methods. The repaint() method called is not defined in the ChartPanel class but in the AWT Component class that ChartPanel ultimately inherits from. It does a bit of internal stuff and then calls ChartPanel's paint method. That in turn just sets which part of which chart is being repainted, and then calls ChartPanel's paint0 method. This is where all the action is.

It is in paint0 that we implement the User's font choice and labeling scheme for symbols, give distint colors to the current Ego, Alter and links, and make the chain of linking kinsmen orange. Here we identify which Special Relationships should be charted and assign them their User-selected line colors.

Repaint call stack: ChartPanel: paint0  ->  KinTermMatrix:getPath  ->  Link:drawSymbol  ->  drawLabel  ->  Person:drawSymbol  ->  drawLabel  ->  Marriage:drawSymbol  ->  drawLines  ->  Context:specialRelationships  ->  Context.SpecRelTriple

Controlling Labels and Display

Aside from moving symbols around, choosing the font size and picking the colors for Special Relationships, the User does not have much control over the chart's appearance. But they can choose the labels on each symbol. Each Person (or Link) symbol can have a full name, first name, last name, initials, or no name. In addition, they may have no kin term, or the kin term of reference, or the kin term of address (if there is a separate domain theory of terms of address), or a letter-label for either type of kin term. The idea behind letter-labels is that if kin terms are long and similar in appearance, it may be easier to see a pattern if all the 'kudosanimoto-parsition' labels are replaced with a simple 'A', the 'kudosonimoto-parsition' labels are replaced by 'B', and so on.

These options are controlled by the 'Labels' drop-down menu in the main SILKin menu bar. In the definition of that menu item, the four options for name are in the button group nameButtonGroup. That makes them 'radio buttons' where selecting one option deselects all others. Likewise, the kin term options are members of kinTermButtonGroup.

Labels Menu call stack: SIL_Edit: noNameBtnActionPerformed  ->  ChartPanel: setNameLabel  ->  nameLabel
All ten options have a similar call stack.

Detail Displays

The Chart Panel that we have discussed so far is the upper pane in the SILKin main window. The lower pane is called the Detail Display. It shows all the data for whichever Person or Union the User most recently clicked on. When it is a Person display, we show a menu of Egos plus the current Alter's Names, Born Year, Died Year, and Last Data Change date. These are all read from their Individual object in the Context's individualCensus. The one or two kin terms that the current Ego calls Alter are read from the Kin Term Matrix. The drop-down menu of Egos is just a list of all Persons in this Context, with the current Ego selected.

If any UDPs have been defined for this context, the Detail Display will include a menu of UDP names and a display of the current value (if any) of the selected UDP.

Individual Details call stack: ChartPanel: personMenu_ListSelect  ->  delayedAreaCheck(person)  ->  showInfo(person)  ->  SIL_Edit: showInfo  ->  PersonPanel: showInfo  ->  restoreLineBreaks  ->  checkForAutoDefs  ->  applyAutoDefs  ->  fillTextField  ->  deSlashify  ->  displayUDPVals

When the Detail Display is for a family, we display the start and end dates for the union, a drop-down menu of reasons for the end of the union (if it has ended), and lists of the family members. For each person in the family, we display their home chart, full name, and serial number. The data all comes from the Family object on the Context's familyCensus.

Family Details call stack: ChartPanel: personMenu_ListSelect  ->  delayedAreaCheck(family)  ->  showInfo(family)  ->  SIL_Edit: showInfo  ->  FamilyPanel: showInfo  ->  PersonPanel: restoreLineBreaks

For either Detail Display, there is a 'Notes' field where the User can write anything they want about the person or family. To give User complete freedom in writing style, we must remove line breaks, double-quotes, and angle-brackets before storing on the Family or Individual object that will later be written to a SILK file -- this will prevent parsing errors. When we retrieve the User's notes from the object, we restore the line breaks but do not restore double-quotes or angle-brackets (user education). Preparing the notes for storage on the object we call convertBannedCharacters and the reversal is called restoreLineBreaks.

Note Reformatting call: FamilyPanel: convertBannedCharacters

Note Reformat Reversal call: PersonPanel: restoreLineBreaks

Kin terms also have to be checked for trouble-making characters. SILKin originally required Users to compose kin terms of only 'regular letters or numbers' and underscores. This was unacceptable to some users. So now virtually all restrictions have been eliminated (as explained in the Help files). Whenever the User inputs one or more kin terms, they are slashified before writing to the object, and deSlashified when copied from the object into a Detail Display field.

Kin Term formatting call: PersonPanel: slashify

Kin Term format reveral call: PersonPanel: deSlashify

Managing Multiple Charts

There are several reasons why a User might want to organize her data into multiple Family Tree charts. (This is discussed in the Help files.) When User first launches SILKin, the label over the chart area says 'Draw Family Tree Charts Below.' When she creates a new context (by beginning a family tree chart) SILKin automatically creates an initial chart (with description 'Default Chart') containing her initial people and families. SILKin assigns the name 'A' to her first chart, and will name subsequent charts 'B' 'C' etc. The name and description of the current chart is displayed in the drop-down menu at the top of the chart area.

Default Chart Creation call stack: ChartPanel: personMenu_ListSelect  ->  getProjectName  ->  SIL_Edit: rebuildChartCombo

As soon as there are one or more charts (named 'A', 'B', etc.) the User can add (or edit) a chart description that will appear in the Chart menu along with the chart name ('A'). For example, the User may want to change the default name 'Default Chart' for Chart A to 'Adams Family.' Editing of chart descriptions is done by choosing Edit  ->  Edit Chart Description.

Edit Chart Description call stack: SIL_Edit: editChartDescriptionItemActionPerformed  ->  FamilyPanel: convertBannedCharacters  ->  SIL_Edit: rebuildChartCombo

Recall that multiple charts are the reason we provide for links to Individuals. Multiple charts are also the reason we identify a person's Home Chart on their Detail Display, and also a drop-down list of all other charts that person may appear on.

Suggestions

Academically speaking, Suggestions are the raison d'etre for SILKin. My thesis focused on ways to make suggestions to User about possible definitions for kin terms identified, and other useful analytic observations. The goal was to speed the process of learning definitions for all the kin terms encountered in the target language. In the real world, users focus on gathering the data, viewing and documenting it in SILKin, and doing their own analysis, often by visual inspection of the various charts. It will be helpful before proceeding to read the section on Suggestion in the Help files.

Recall that a Context always contains a DomainTheory for terms of reference. It may optionally also have a DomainTheory for terms of address. The slot (field) that holds suggestions on a DomainTheory is called issuesForUser. The data structure is discussed here. When the User clicks on Get New Suggestions, SILKin launches a complex analysis of the updated SILK file. The major steps in this analysis are, in effect, SILKin's Learning Strategy:

  1. Save the SILK file with any data User has added in this session.
  2. Load the Library's predicate encodings and decodings, if not already loaded.
  3. Create and load a (Learned_DT) learner with terms of reference from the current Context.
  4. Truncate UDP names (if any) in any of the data.
  5. Launch active learning by that learner on the SILK file.
  6. Clean up any 'side effects' of learning that may have littered the Context.
  7. Store any suggestions on the domain theory and locally.
  8. Repeat steps 3-7 for terms of address, if there are any.
  9. Re-save the SILK file, now containing suggestions.
  10. Create an HTML file of the suggestions, formatting it with Silk-status.xsl.
  11. Activate the menu choice 'Act on Suggestions'.

Get New Suggestions call stack: SIL_Edit: getSuggestionsItemActionPerformed  ->  doActiveLearning (expanded below)  ->  DT_Abstract2: resolveSynonymsInDyads  ->  DomainTheory: postAnomaliesForUser  ->  Learned_DT: learnKinTerm (see detailed expansion below)  ->  DomainTheory: detectSynonymsAndUmbrellas  ->  proposeUmbrellas  ->  DT_Abstract2: analyzeSynonymsAndUmbrellas  ->  validateSynonymsAndUmbrellas

Step 2 enables a space-saving efficiency hack that allows us to retrieve definition components from Library domain theories (complete sets of definitions from other cultures) via a code rather than the full domainTheoryName-kinTerm-clauseNumber.

Step 4 helps the learner to compare kin types in the target context that may contain UDP names with Library kin types that also contain UDPs. For learning purposes, it is sufficient to know that our data and the potentially-matching Library definition both have UDPs; their names are of no use.

Step 10 creates a file that the User could examine with certain web browsers. Normally, they will examine it in a special window for acting on suggestions; that window requires an HTML format also, so we just get that task out of the way.

Step 5, of course, is the heart of the matter. After setting local flags and variables, we make a first loop through all the kin term definitions that have been accepted so far as 'probably accurate.' We test each of these definitions against all the data gathered to date, and bring to the User's attention any data that seems at odds with an accepted definition.

Then we do a second loop, examining all the kin terms for which no definition exists. Whenever there is enough data for a term, we attempt to learn a definition or make other helpful observations about the term. This uses the learner's learnKinTerm method outlined below. Finally, we look for any synonyms or umbrella terms that can be identified.

Active Learning call stack: SIL_Edit: getSuggestionsItemActionPerformed  ->  doActiveLearning  ->  Learned_DT: learnKinTerm  ->  Library.ClauseIndex (constructor)  ->  Library.ClauseCounts: loadFromDisk  ->  Library.CB_Ptr: clearCache  ->  Library.KTD_Ptr: clearCacheDTs  ->  MainPane: treeMapDeepCopy  ->  DomainTheory: makeNEG  ->  DT_Abstract1: countLeaves  ->  Learned_DT: containsChartableUDPs  ->  DomainTheory: findCandidates  ->  evalCandidates  ->  findKTMatches  ->  dataSelection  ->  proposeDefinition  ->  findBaseCBMatches  ->  makeAnomaly  ->  postAnomaliesForUser  ->  detectSynonymsAndUmbrellas

Representing Definitions

Early in the academic effort, we had to choose a standard representation for kin term definitions throughout SILKin. There were three basic choices; we chose to use Horn Clauses (i.e. PROLOG syntax) as explained here. It is not only the most precise representation, but the only one that is easily understood by humans and computers. To the human eye, a Horn Clause definition looks like this:
uncle(Alter, Ego) :- parent(P,Ego), brother(Alter,P).
                 | parent(P,Ego), sister(S,P), husband(Alter,S).

But to SILKin, a kin term definition is an object containing other objects, namely:

Anatomy of a KinTermDefinition or KTD object
Field Contains Remarks
clauseHead Literal the head literal of the Horn Clause e.g. uncle(Alter,Ego)
kinTerm String the term being defined e.g. uncle
eqcSigExact String the signature of an equivalence class (EQC) that would hold this definition
eqcSigStruct String the signature of an EQC that would hold a gender-neutral (structural) version
comments String supplied by User
gloss Gloss possibly empty
definitions ArrayList of ClauseBody objects in any legal Horn Clause form, perhaps written by a human. (See below for description of a ClauseBody object.)
expandedDefs ArrayList of ClauseBody objects full expansion of the definitions into all-primitive form; always computer-generated
exactEQCs TreeMap the exact EQCs to which this KTD's clauses belong
structEQCs TreeMap the structural EQCs to which this KTD's clauses belong
headPred Predicate object a Predicate object whose name is the kin term we are defining
domTh DomainTheory a back-pointer to the theory containing this KTD

NOTES:

  1. SILKin was coded beginning in 2001. The goal was an app that would run at reasonable speed on the laptop of a Wycliffe field worker, possible a bare-bones PC. Thus, a number of fields are explicitly contained that could have been computed on the fly. But fast run times were deemed more important than compact records.
  2. The String, TreeMap, and ArrayList classes are built into Java. They are used extensively throughout SILKin, and are discussed below.
  3. A key distinction must be made between definitions and expandedDefs. SILKin allows a User to manually enter a definition if they know it. And the User is allowed to use in her definition three different classes of predicates (relationships):
    1. primitive predicates (i.e. predicates built into the code): father, mother, parent, child, husband, wife, spouse, son, daughter, male, female, elder, younger, equal, not, divorced, dead, and gender.
    2. standard macros (i.e. a standard set of definitions that is pre-loaded into SILKin): sibling, siblings, brother, sister, half_brother, half_sister, step_brother, step_sister, step_father, step_mother, spice (plural of spouse), parents, and children.
    3. cultural terms (i.e. terms in the language being studied that have already been defined in SILKin): for example, if the term 'cross_cousin' has already been defined then it may be used in other definitions.

    When a human enters a definition, in Horn Clause form, it is stored in the definitions field as an ArrayList of ClauseBody objects. The Predicates of its Literals may come from any of the three classes. But in SILKin's internal computations, it only wants to deal with primitive (i.e. built-in) predicates. So whenever a KinTermDef is constructed, SILKin computes the expandedDefs version, in which every non-primitive predicate is replaced by its definition in all-primitive terms. This usually results in a great expansion of the size of the definition.

    For example: One of the four clauses that defines the English term "aunt' is
    mother(M,Ego), brother(B,M), wife(Alter,B).

    Now 'mother' and 'wife' are primitives but 'brother' is not — it is defined by a pre-loaded macro that says a brother is a male child who shares both of your parents. Therefore, this part of the definition of 'aunt' is expanded into:
    mother(M,Ego), mother(G,M), father(H,M), child(B,G), child(B,H), male(B), not(equal(M,B)), wife(Alter,B).
    That expansion is then stored in expandedDefs and used in all calculations.

  4. If we ignore things like seniority or clan membership, then any kin type can be described or specified by the shortest path from Ego to Alter. Using standard abbreviations for the primitive (and standard macro) relationships (e.g. Mo = mother, Stbro = step_brother), we can concatenate the abbreviations for the path from Ego to Alter into a unique identifier. We call this concatenation a pcString. For example, the pcString for the 'aunt' clause above is 'MoBroWi' — mother's brother's wife.

    If we also ignore the gender of the linking kinsmen we can make a more general concatenation we call a pcStringSructural. The pcStringStructural for the above clause would be 'PaSibSp' for parent's sibling's spouse. That string would also apply to one of the clauses for 'uncle.' An uncle and an aunt definition are 'structurally identical' if they are the same when gender is ignored.

    If we were to take all the pcStrings for a definition, sort them alphabetically, and then concatenate them, we would have the kin type signature-exact of the definition. If we use the pcStringStructural, the concatenation will be the kin type signature-struct.

  5. The objects contained within a KinTermDef object are described below.

Anatomy of a ClauseBody object
Field Contains Remarks
body ArrayList of Literal objects list of all the literals in this clause. (See below for description of a Literal object.)
expansionPath ArrayList of Strings a list of strings that document the sequence of cultural kinTerms and standard macros that were expanded into their all-Primitive meanings in the course of expanding THIS ClauseBody into its all-Primitive form
seqNmbr int a 'serial number' for this clause in the KTD. This allows referring to and fetching clauses efficiently.
pcString String a concatenation of the abbreviations for the linking kinsmen from Ego to Alter.
pcStringStructural String a concatenation of the abbreviations for the linking kinsmen from Ego to Alter, but using gender-neutral terms.
ktd KinTermDef a back-pointer to the enclosing KTD.
A ClauseBody object has other fields used only for internal processing. They are not documented here.



Anatomy of a Literal object
Field Contains Remarks
predicate Predicate object This is the relationship, or the verb, in this Literal. For example: in the literal mother(M,Ego) the predicate is 'mother.'
args ArrayList of Argument objects a list of the arguments of the predicate. In the above example, the two arguments are 'M' and 'Ego.'



Anatomy of a Predicate object
Field Contains Remarks
name String The name of this predicate.
category a subclass of PredCategory There are 2 subclasses: CulturalCategory (a predicate defined in the local culture) and PrimitiveCategory (one of the built-in predicates, or a standard macro).



Anatomy of an Argument object
Field Contains Remarks
argName String the name of this argument, as it would appear in a printed Horn Clause.
argType String The name of a subclass of Argument: Variable, Literal, Individual, Constant, ArgString, or StackMarkerObj. This identifies the type of this Argument object.
valueType String The name of a data type or class allowed as a value for this argument: string, integer, float, boolean, symbol, or default. This identifies the type of object that can be added to the value ArrayList.
value ArrayList of Objects A list of things that are the value of this argument. Normally this list has size = 1 because most arguments have a single value e.g. a person or a number or a boolean. But the predicate 'not' takes as it's single argument one or more literals. Therefore we use this very general 'type' for the value of an argument.

Comparison to Library Definitions

As described above, a key step in SILKin's learning strategy is to compare the data for an undefined kin term in the target language with the definitions of kin terms in other languages that are stored in the Library. For each of the 50+ languages in the Library, all of its kin term definitions (KTD's) are recorded in the Horn Clause format described above. Each of those definitions has one or more pcStrings that encode the kin types that are covered by that definition. The English language KTDs, for example, are stored in the Library. The English KTD for 'aunt' has 4 clauses, each with a distinct pcString: MoSis, FaSis, MoBroWi, and FaBroWi. If we sort the 4 pcStrings into alphabetical order and concatenate them with underscore characters, we create a signature for the 'aunt' KTD: FaBroWi_FaSis_MoBroWi_MoSis. No matter how a definition is stated, no matter in what order the clauses are listed, the sorted and concatenated KTD signature will always be the same if the set of kin types covered is the same.

Thus the KTD signatures for uncle, senior uncle, and junior uncle will always be the same. We say that those three KTDs belong to an equivalence class or EQC because their signatures are identical. That does not mean the definitions are identical, just that they all point to the same set of kin types. The SILKin Library folder contains the KinTermSigTree file that is used to build a TreeMap where the keys are KTD EQC signatures and the values are the EQCs — sets of definitions that share that signature. So the 'eqcSigExact' field on each KTD is its index into that TreeMap.

Likewise, the pcString for each clause (the pcString field of the ClauseBody object) is the index into a TreeMap of clause EQCs. The ClauseIndex file in the Library folder is used to build that TreeMap.

These indexes into self-balancing TreeMaps make for very efficient comparison of the data for a kin term we are trying to learn with Library KTDs. Here are the steps of the process:

  1. Compute the pcString of each dyad for the kin term. A dyad records an Ego, an Alter, and the kin term Ego uses for Alter. It also has a list of the linking kinsmen who constitute the path from Ego to Alter, and the pcString for that path (i.e. the kin type)
  2. Compute the set of all pcStrings from these dyads. These are the indexes of the clauses that should be contained in a correct definition, based on the data gathered so far.
  3. Use the ClauseIndex TreeMap to find all the ClauseBodies that cover pcStrings (kin types) in that set. Then use the 'ktd' pointer on each Clausebody to compute the set of KTD EQCs that cover at least one kin type of the undefined term.
  4. Examine each KTD EQC and select those that cover every kin type in the set, but not any kin types that are covered by other terms in the target language.
  5. Each of these KTD EQCs could be a match with the kin term we are trying to learn.
    1. If one KTD EQC is an exact match, covering exactly the kin types covered by the kin term we are trying to learn, then we make this a Proposed Definition.
    2. If one KTD EQC looks like an exact match except for a small number dyads, then we bring this to the User's attention and ask if the few mis-matches are perhaps erroneous.
    3. If all the KTD EQCs cover more kin types than the term we're trying to learn (but not any that are kin types of a different kin term in the target language), then we compute which new piece of data (dyad) would be most helpful in narrowing the field of candidates or confirming that the kin term we are trying to learn also covers the additional kin type. We generate a Data Request for 3 instances of that dyad.
    4. If none of the above has borne fruit, and if the User has turned on the 'do Base CBs' flag, try to compose a compact definition (Horn Clause) out of the Library ClauseBodies that covered the same kin types as our undefined kin term.

Anomalies, Synonyms and Umbrellas

Notice that our comparison algorithm looks for Library definitions that cover the kin types of the term we are trying to define, but not any kin types that are covered by other terms in the target language. That algorithm fails completely when the term we are trying to define has a synonym.
For example: if we were trying to define the English term 'dad' but our data included dyads for both 'dad' and 'daddy,' then at least some of the kin types for 'dad' would be covered by 'daddy.'

Therefore, our learning strategy includes searching for synonyms. We look for dyads with the same kin type but different kin terms.

Data Requests

As mentioned above, the algorithm for comparing our data with the definitions stored in the Library will sometimes make a Data Request of the User. Since our overall goal is to minimize the amount of data that must be acquired, we want to request the most powerful piece of data — the one which can make the largest reduction in the candidates. The best datum would be a kin type that is found in half of the KTD EQCs (i.e. the binary search algorithm). If the User finds that this kin type is not covered by the term we are trying to define, then we have ruled out half of the candidates. If she finds that it is covered, then the other half of KTD EQCs is eliminated. Such an ideal kin type may not exist, but we choose one as close to the ideal as possible and ask User for 3 dyads for that kin type.

If the User obtains the requested data, it will narrow the search the next time the Learning Strategy is used. Hopefully, sending her off in search of those 3 dyads will trigger conversations with her informants that may provide insights into this term's meaning.

Action Boxes for Suggestions

The User controls when the Learning Strategy is invoked. When she feels that sufficient data has been gathered, she clicks on 'Context  >  Get New Suggestions.' To see the suggestions generated by the Learning Strategy (if any), she then clicks on 'Context  >  Act on Suggestions.' This will take her to a new window with two panes.

      Twin Pane Action Window

The User can choose a suggestion to process either by choosing one from the drop-down menu or by clicking on one in the 'Pending Issues' list in the right pane. Either action will change the left pane to the appropriate Action Box, and the right pane to some detailed instructions. In the Action Box the User can choose to take no action, accept the suggestion, or reject it. More details about the User's options are found in the 'Act on Suggestions' section of the User Manual. That section also describes the effect of each User decision on her data and on the Learning Strategy.

Editing Preferences

The Editor Settings section of the SILK file specification identified many parameters that a User may adjust for each project. A few of them can be edited directly:

The remaining parameters, that we expect to be changed rarely, are controlled via an editing window reached via 'Edit > Edit Prefs'. The 'Editing Preferences' chapter of the Help system gives a detailed description of each parameter and how it affects SILKin's behavior. The button labeled '?' next to each parameter takes the User to the Help section describing that parameter.

        Edit Preferences Window

Each time this window is launched, the current settings for each parameter are loaded from static fields (properties) of the 'Library' class, and any changes by the User are written to those fields. The parameters are written to the 'Editor Settings' section of the SILK file whenever it is saved, and read back in each time the SILK file is loaded.

Editing the Context

         Context Editing Window

The context editor is a tool that was created early in SILKin's development, before the GUI was built. So much of its functionality has been superseded by the editing features of the Chart pane and the Detail Display. But a few functions remain relevant. This is the only tool for un-deleting a person or union. And it is the only way to edit a Domain Theory or create/edit a UDP.

Creating and Editing UDPs

User Defined Properties (UDPs) are described fully in the Help system. The Context Editor window (above) is launched by choosing 'Edit > Edit Context'. Then the User clicks on the 'Add UDP' button to launch the UDP Editor. Or, selecting a UDP to edit from the drop-down list of previously created UDPs will launch the same window, with that UDP's features and parameters loaded into the window.

        UDP Editor Window

When a UDP has been defined (or revised) the User must click the 'Check for Errors' button before saving the UDP. This is because there are numerous consistency checks; for example, if this UDP is restricted to certain values, then a list of legal values must have been entered. If any consistency requirements are violated, an error dialog box pops up describing the error and how to fix it. If no errors are detected, then the 'Check for Errors' button is changed to a 'Save' button.

When a UDP is created, it is added to a TreeMap of UDPs (where the key is the UDP's name) in the 'userDefinedProperties' field of the current Context object. The UDP object itself is intended to serve as both a template at the Context level and also a specific UDP attached to a particular person.

Anatomy of the User Defined Property Object
Field Data Type Description
starName String The name of this UDP, which must begin with a star (asterisk).
typ String The type of value this UDP must have. Valid types are:
   - integer
   - float
   - string
   - boolean
   - individual
   - Individual
or an array of one of those types.
singleValue boolean True if this UDP must have only one value.
chartable boolean True if this UDP can create a visible link between two symbols on the chart.
connects boolean True if this UDP can create an invisible link between two individuals in the population.
sameVal boolean True if this UDP creates connections when two individual have the same value for this UDP.
validEntries ArrayList of Object A list of all the legal values for this UDP.
value ArrayList of Object This list holds the single or multiple value(s) for this particular instance.
defaultValue Object If non-null, this default value will be assigned to every individual as soon as they are created. The default can be overridden by the User in the Detail Display for the individual.
minVal Number object If this UDP has a float or integer type, this specifies the minimum value that can be legally assigned.
maxVal Number object If this UDP has a float or integer type, this specifies the maximum value that can be legally assigned.
chartColor Color object If this UDP creates a visible link on charts, this is the color of those lines.

When a new person is created, the Context is searched for UDPs. For each one found, a clone of the template UDP is created and stored on the Individual object's 'userDefinedProperties' TreeMap. Any value assigned by the User via the Detail Display is stored on this person's instance.

Deleting People and Unions

Users can delete a Person or a Union on the Chart by control-clicking on their symbol. There are some validity checks, e.g. you can't delete a Union (Family) if there are still people in it. But once the deletion is valid, one of two things happen:

The same is true for unions (families).

To un-delete a person, select them in the drop-down menu of individuals in the 'Current Population' area of the Context Editor. That will bring up a Person Editor window.

        Person Editing Window

The Person Editor will have a top message "This is a DELETED record." and a button for un-deleting this person. Notice that none of their data is missing — they simply became invisible and unreachable while deleted. However, before they could be deleted any links between them and another symbol had to be removed, so the prior relationships are missing. It is likely that their previous location on their home chart has been filled by something else, so we do not restore them to their prior x/y coordinates. Instead, we put them in the upper left corner of their home chart. They then can be dragged to wherever the User wants them. All of a person's data can be edited in the Person Editor, but it is probably easier to do so in the Detail Display on the main SIL_Edit screen.

The procedure for un-deleting a Union (Family) is the same. But because no relationships are saved for a deleted record, and the sole purpose of a Union is to record family relationships, there is no point to un-deleting a Union except perhaps to retrieve comments that were posted to this Union.

Domain Theory Editor

One other editing function is available on the Context Editor: editing a Domain Theory. There are several reasons why you would want to do this, summarized in the Help pages. The Domain Theory contains all the kin term definitions obtained so far, expressed as Horn Clauses, whose structure was specified above. Horn Clauses are fairly easy for humans to understand, but their syntax is quite strict and SILKin places some restrictions on the names of variables and predicates. There is a lengthy discussion of all this in the Help pages.

The Theory Editor is designed to help a User to manually enter a definition they have learned, or to edit an existing definition. It is launched by clicking on the 'Edit Theory' button on the Context Editor.

        Theory Editing Window

Because the Help system describes this process so thoroughly, we will discuss here only the processes going on behind the scenes. The top two fields on the Theory Editor are to facilitate publication. The primary author would be the first name to appear on a scholarly paper, and the citation should be the URLs for all publications about this domain theory (kinship system).

Kin Terms & Edits in Progress is a drop-down menu containing three lists:

  1. The first list holds all kin terms in the target language for which an edit has been started but not successfully completed (if any).
  2. The second list holds all kin terms in the target language that have accepted definitions (if any).
  3. The third list holds all kin terms found in User's data that do not have accepted definitions (if any).

Auxiliary Terms is a drop-down menu of all the auxiliary terms that User has defined so far, if any. It also has a menu item 'Define New Auxiliary Term.' Deciding that a term is an auxiliary, not a kin term, is User's decision. Any auxiliary must contain the string '[aux]' in the term's name, somewhere after the first character, which must be a lowercase letter. This rule is enforced by a validity check in the EditTheoryFrame.getValidAuxTerm() method. When an auxiliary is created by the User, it is subjected to the same syntax check as a kin term definition. But no dyad check is performed, since by definition auxiliaries are not kin terms and have no dyads.

The large area below these two drop-down menus is the editing area. There are three radio buttons below it that control editing. The default button selection is 'Display NOT Editable.' Until this selection is changed, the editing area can display anything, but no changes may be made. The other two buttons allow editing of whatever is in the editing area. The 'Edit Def' button allows editing of any Horn Clause that is chosen from either drop-down menu.

A User may attach comments to any person or family. They may also attach comments to any definition or to the domain theory as a whole. To add or edit a definition comment, the definition should be chosen in a drop-down menu and then the 'Edit Comments' button should be chosen. SILKin will then write that definition's comments (if any) into the editing area, and when User finishes any changes will be written back to the comments field on the KTD. If the User wants to edit the comments on the entire domain theory, she should scroll both drop-down menus to the top and then choose 'Edit Comments.' This will access the comments field on the DomainTheory object.

The editing area is a large text field. When a kin term is selected, SILKin generates the standard head predicate for it. For example, if the undefined kin term 'ant' has been selected from the left-hand drop-down menu, the text

ant(Alter,Ego) :-

will be written into the editing area, and a KTD for that kin term would be created in the domain theory. (For an already-defined kin term, the KTD for that kin term will be retrieved from the domain theory, and any Clausebody objects in the KTD's 'definitions' field will be printed in Horn Clause format into the editing area, using the ClauseBody's 'toString()' method.) Once clause(s) are printed or entered into the editing area, they are simply strings of characters, and the editing area does not enforce any syntax rules, so it would be easy for the User to edit the text in a way that violates the rules of Horn Clause syntax.

The row of buttons and menus below the radio buttons are designed to help the User create Horn Clauses in proper syntax. 'Pre-Defined Terms' is a drop-down menu of all the English kin terms that SILKin recognizes as legal predicates. 'Defined Local Terms' is a drop-down menu of all the kin terms that have accepted definitions in the target language plus all auxiliary terms the User has defined; these may also be used as predicates in any clause. Thus, when the User wants to enter the predicate for the first Literal of a clause she can scroll to the desired term in either drop-down menu, then click on the adjacent 'Insert' button. If the 'Generate Variables Automatically' box is checked, SILKin will insert the predicate and a pair of parentheses. The first argument inside the parentheses will be the earliest uppercase letter not yet used in this clause; this will be the first variable in the Horn Clause. The second argument inside the parentheses will be Ego (if this is the first literal) or else the first argument of the previous literal. The two arguments will be separated by a comma, and the closing parenthesis will be followed by a comma and space. Thus, after our User has chosen the first two predicates for her clause, SILKin will have printed

ant(Alter,Ego) :- father(B,Ego), brother(C,B),

in the editing area. Note that we did not use 'A' as a variable name, nor will we use 'E'. This is to prevent confusion with the variables Ego and Alter.

If the User completes her first clause by inserting the predicate 'wife', SILKin will generate a third literal, producing this:

ant(Alter,Ego) :- father(B,Ego), brother(C,B), wife(D,C),

When she clicks the 'Period' button, SILKin will change the 'D' to 'Alter' and change the final comma to a period, creating the finished Horn Clause.

ant(Alter,Ego) :- father(B,Ego), brother(C,B), wife(Alter,C).

To enter the second clause of her definition, she will choose the first predicate and hit 'Insert' as before. Because the prior clause ends with a period, SILKin will generate the 'OR' symbol ('|') and her first literal. Note that we start with the variable 'B' again; each clause stands on its own. We now have

ant(Alter,Ego) :- father(B,Ego), brother(C,B), wife(Alter,C).
                               | mother(B,Ego),
and so on, for an unlimited number of literals and clauses.

When the User has finished a definition, she hits the 'Syntax Check' button to check for errors. If none are found, the 'Dyad Check' lights up; it triggers a verification that this definition fits each Ego/Alter pair in the data that is labeled with the kin term 'ant'. Any mis-matches are brought to her attention. If no problems were found, then the 'Accept Def' button lights up; it will install this KTD in the domain theory. At the User's option, SILKin will also add a dyad for this kin term to every pair of individuals that satisfy a clause of the KTD.

Finally, the User can cancel the entire edit or creation with the 'Delete This Edit' button.

Rather than provide a Call Stack for each action involved in editing, I will cite the collection of methods involved in this process. Their interactions should be self-evident. Unless otherwise noted, all these are in the EditTheoryFrame class.

Methods used to load the current domain theory into this editor:

'Slashification' requires explanation. The current state of a Context is written to a SILK file at the end of each session, and reloaded at the beginning of the next session. The original parser written to read in a SILK file expected no carriage returns in the middle of text strings, no embedded blanks in names, etc. But once real users got involved, they requested relief from all those expectations. So we established a convention:

Any character in a string that would throw the parser a curve must be preceded by a backslash before it can be written to a SILK file. After a Context is loaded from a SILK file, any string containing a backslash must have the backslash removed before display to the User.

Thus we have the methods PersonPanel.slashify(String) and PersonPanel.deSlashify(String). Once this convention was implemented, we also tasked the 'slashify' method with replacing any prohibited characters in comments with their proper substitutes (i.e. double-quote characters are replaced with single-quotes). These latter substitutions are not reversed by 'deSlashify'; hopefully users will learn from seeing their illegal characters corrected.

Methods used to read and write Horn Clauses to/from this editor:

Methods used to check syntax of Horn Clauses:

The strategy of syntax checking is to create a temporary blank context. The text from the edit area is parsed into a series of Horn Clauses. Then install this new or edited definition Horn Clause as the only KTD in the domain theory (reference) of the temporary context. Create a population of only two people, one male and one female (guess which method that is). Then call a method that builds an example of each clause of this KTD in the temporary context; it finds or creates a person to satisfy the constraints on each variable in each clause. If all that can be done without triggering any exceptions or encountering logical errors, then the syntax of the text in the editing area must be OK. So we destroy the temporary context and restore the original one. Finally, we enable the 'Dyad Check' button.

If any syntax errors are discovered, we describe them to the User and, if possible, suggest corrections using 'decodeException'. The 'Dyad Check' remains disabled until all syntax errors are fixed.

Methods used to check this definition against existing dyads:

All the action occurs in the 'dyadTest()' method that uses the fields of this instance of DyadTestReport. Here is the strategy of that method.

Iterate through all the dyads for this kin term, grouped by pcString (kin type). Try to match each dyad's kin type with the pcString of an expandedDef of the proposed definition. If any dyads don't match, put them in the 'mis-fits' list. If a dyad matches multiple expandedDefs for its kin type, then test that dyad against each expandedDef until we find a good fit. If we run out of expandedDefs with no fit, put this dyad in the 'mis-fits' list.

Recall that an expandedDef is a ClauseBody containing only system-defined predicates (e.g. father, son). We test a dyad against such a ClauseBody by calling the 'fit(ClauseBody, Dyad)' method above. Using the user's context, we start with the person who is Ego in this dyad, and then follow the 'instructions' in the ClauseBody to reach its Alter. If the Alter in our dyad is (or could legally be) the Alter in the ClauseBody, then this dyad is a 'good fit' for the definition in the ClauseBody.

For example: if we are testing dyads for the kin term 'cousin' then one of the ClauseBodies in the expandedDefs of 'cousin' will be a Horn Clause for father's brother's son. The pcString for that ClauseBody will be 'FaBroSo'. If we want to test a dyad whose pcString is also 'FaBroSo' then we will start with the person who is Ego in that dyad and identify his father. For each of his father's full brothers we will check each of his sons to see if he is the Alter of the dyad. If so, signal success and halt the test. If we end the test without ever reaching the Alter of the dyad, then signal failure.

In this example, you might think there is no way the dyad could fail the test. But recall that a pcString does not contain constraints; a Horn Clause may contain constraints, such as a requirement that person A is older than person B, or A has the same clan membership as B, etc. So failure to satisfy a constraint will always produce failure. Perhaps a different expandedDef — with the same pcString — will have constraints that match this dyad, or perhaps will have no constraints at all.

As mentioned above, when both the Syntax Check and the Dyad Check are completed successfully, the 'Accept Def' button is enabled. If User hits this button, the KTD is added to the current domain theory and installed in the list of defined kin terms in the drop-down menu. A pop-up window will offer the option of creating a dyad for each Ego/Alter pair in the context who meet the definition of this kin term. That is accomplished efficiently using the Kin Type Index.

Editing a domain theory is a complicated process, and it probably will only be attempted by experienced users or Anthropology Coordinators. Please note that in the upper right corner of this screen is a Help button (labeled '?'). Clicking on this will bring up the Help page for this process in a new window.

Exporting a Kin Term Matrix

The Kin Term Matrix is used internally to facilitate changing Ego. The matrix has one row for each person in the context, and also one column for each person. Each cell of the matrix is a data structure (Node) that contains the kin terms that the row (Ego) calls the column (Alter). Thus each row can be read as the relationships of each row's Ego to all other persons.
Most users will prefer to look at that information in the main SIL_Edit screen that shows family tree charts and Detail Displays. But for folks who are most comfortable with spreadsheets, we provide the ability to export the matrix as a tab-delimited text file (.txt).

Excel and Apple's Numbers are both able to import these files, and possibly other spreadsheets as well. Note, however, that SILKin cannot import them. Therefore, changes made to an exported Kin Term Matrix will not affect SILKin in any way.

The Help System

The Help system is pretty self-explanatory (no pun intended), but its structure deserves comment. There is a 'Help' menu on the top menu bar of the SIL_Edit window, and also a 'Topics' menu on the top menu bar of a help window. It is important that these two menus remain identical, so they are each instances of the HMenu inner class in the HelpFrame class.

        Help Window

Each menu item in an HMenu invokes the HelpFrame.displayPage(int, String) method. The first argument is the integer code of a particular HTML file in the 'Library/Resources/Help Files_XX' folder (where 'XX' is the 2-character code for the current menu language). The second argument is a String containing the name of a section (an invisible anchor) inside that file. So clicking on a menu item in either menu causes a particular section of a particular HTML file to display in the helpTextPane. This pane is a Java Swing JEditorPane; these are capable of displaying arbitrary HTML pages. All the nice behaviors of a web browser are built into a JEditorPane. I added nothing to it.

I did add a 'Back' button to the HelpFrame. This operates on a push-down HelpFrame stack storing the page/section pairs that have been displayed. Clicking the Back button displays the most recent previous page/section and adjusts the stack. Clicking it again goes to the page before that, etc. The 'Forward' button operates analogously. The stack is updated every time the HelpFrame.displayPage(int, String) method is used to load a new page or section.

At the bottom of the window, a check box controls whether the Help window is displayed as soon as SILKin is launched. The default value for a new installation is TRUE but the user can change it by un-checking this box. The choice is stored in SIL_Edit.helpScreenOnStartUp.

SILKin's Administrative Functions

I mentioned that editing a context is a rare activity, perhaps only tried by Anthropology Coordinators. There is a collection of administrative functions that are intended to be used by only one person, the International Anthropology Coordinator (IAC) for SIL (or someone working for them). These are the functions that maintain the library of known domain theories and update the various indexes of them.

When the 'Edit > Edit Context' menu item is clicked, SILKin displays the MainPane window with a ContextEditor window contained within it. The 'Edit > Admin Mode' menu item produces a pop-up requesting the admin password (hard-coded to 'SIL'). Once that is given, the same MainPane window is produced, but the 'Admin' top-level menu item is enabled, along with all menu choices in the File menu. So in Admin Mode you can edit the user's context or domain theories, but also can do library maintenance.

Adding Domain Theories

The menu choice 'File > Add to Library... > Domain Theory' allows an administrator to add a new Domain Theory file to the SILKin library. In the Library, kinship terms of reference will be a file with extension '.thy' whose base name is the name of the language. If there is a distinct set of terms of address in this language, a second '.thy' file will have a base name of the language name followed by '(Adr)'. A '.thy' file is in a special syntax, explained here. For example: the Cogui language has separate domain theories for terms of reference and for terms of address. So two files are found in the 'Library > Domain Theories' folder:

When a field worker submits a complete domain theory to the IAC for addition to the SILKin library, they must submit a '.thy' file. (If there are distinct terms of address, there will be two '.thy' files.) The field worker must choose 'Edit > Edit Context' to open the MainPane. In that window they must choose 'File > Export File... > Domain Theory(.thy)'. They can choose some convenient location for the file(s) and then SILKin will write a .thy file that mimics human preparation. (Or two files if there are distinct terms of address.) There are no expanded definitions, or families or individuals, or any of the other data included in a SILK file; just the definitions plus some header information. This compact file can be emailed to the IAC.

Export File call stack: MainPane:actionPerformed(e="export domain theory")  ->   eraseExpansions(DomainTheory)  ->   Library:writeThyFile(DomainTheory)  ->   [various JFileChooser methods]  ->   Context:convertDoubleQuotes(comment-string)  ->   DomainTheory:toThyFile(target-filepath).

If the field worker and IAC wish, the SILK file can also be sent to the IAC. But many mail systems will balk at the size of that file, privacy advocates may object to the inclusion of personal information about persons interviewed, etc.

When a new domain theory is received, the IAC must add it to the master copy of the SILKin Library. The IAC does this by launching SILKin and choosing 'Edit > Admin Mode' and entering the password (SIL). This opens a full-featured MainPane. Then she chooses 'File > Add to Library > Domain Theory'. She points the File Chooser to the new file, and SILKin does the rest.

Add Domain Theory call stack: MainPane:actionPerformed(e="add domain theory") ->   [various JFileChooser methods]  ->   Library:loadNewDomTh(filepath) [that calls standard parsing methods]  ->   readDistributionsFromFile()  ->   DomainTheory:computeFeatureVector  ->   Library:postDistributions  ->   DomainTheory:findOverlappingTerms  ->   findHiddenNeuterEgos  ->   toThyFile(target-filepath)  ->   Library:saveContextToDisk  ->  

This action creates a DomainTheory (and its enclosing Context) in memory, adds this to any menus of theories that may exist, computes a Feature Vector for it, performs some validity checks on the theory, then writes out a full, SILKin-generated .thy file (complete with extended definitions) and places it in the master Library. A .ctxt file is also generated for the 'Library > Contexts' folder.

Recall that the Contexts.stub file is the record of which contexts exist and how many domain theories they have. This file will be updated automatically every time we add or delete a context or domain theory.

There is one problem that shouldn't ever occur, but it might if the people maintaining the SILKin code make major changes to certain classes. Those changes might cause SILKin to not recognize the disc copies of objects in those classes, and that would generate an error message like this:         Error Message
To recover from this error, the IAC must use the following procedure:

  1. Choose 'File > Delete from Library > All'. This will erase SILKin's internal copies of the kinship systems -- not the originals.
  2. Make sure that in the Library folder, there is a folder 'Domain Theories' (provided with your standard installation) plus two other empty folders. I suggest naming them 'Dom Th Additions' and 'Dom Th Done' for clarity, but the names don't matter. The domain theory files in the 'Domain Theories' folder must be added in small batches of 7-10. This is to avoid exceeding the memory capacity of your computer. One of the standard theories, Cubeo Addr, is so memory-intensive that it must be added singly by itself. (There is an empty folder in the 'Domain Theories' folder that reminds you.)
  3. Move the first 9 files into the 'Dom Th Additions' folder.
  4. Choose 'File > Add to Library > Batch Add Dom Thys'.
  5. When the file chooser dialog opens, navigate to the folder containing those nine files. You must choose the folder, not the contents.
  6. SILKin will process those domain theories one at a time, and then display a pop-up message saying that 9 new files have been added to the Library.
  7. Manually move those 9 files from 'Dom Th Additions' to 'Dom Th Done'.
  8. Next choose 'File > Add to Library > Domain Theory'. When that dialog box opens, navigate to the Cubeo Addr file in the 'Domain Theories' folder. In this case, you must choose the file, not the folder.
  9. SILKin will add this file to the Library; it might take a while.
  10. Now repeat steps 3 - 7 for each batch of 7-10 files until all of them have been added. You should end up with all the '.thy' files in the 'Dom Th Done' folder, and only the empty reminder folder in the 'Domain Theories' folder. Now move all the files from 'Dom Th Done' back to the 'Domain theories' folder.
This procedure will make SILKin re-create all the Library's ancillary files in formats that the current code can recognize. If no brand new domain theories have been added, there is no need to re-create all the indexes; they are unaffected.

Deleting Domain Theories

The IAC might need to delete a doman theory from the Library, most often when a new or updated version of one is received. SILKin will not allow you to overwrite an existing domain theory; you must first delete the old version, then add the new version. When in Admin Mode, she will choose 'File > Delete From Library...' This brings up a menu of all the contexts in the Library. They will be in alphabetical order except for any context (or domain theory) that has been added in the current session; current additions are listed last. The first item in the list will be 'All' which allows the entire Library to be erased with a single command. Caution is advised.

Once the context has been identified, the IAC may choose to delete one or both of the domain theories, or the entire context. Deletion may include erasing the related file(s) from disk, or simply removing them from the Library.

Here is the call stack for deleting a single domain theory.
Delete from Library call stack: MainPane:actionPerformed(e="delete from library") ->   Library:retrieveOrCreateStub(name-of-deletion)  ->   [a JOptionPane presents choices re: what and how to delete]  ->   removeContextStub(context)  ->   writeStubFile();  ->   readContextFromDisk

Recall that SILKin maintains many pre-computed indexes on the Library to speed up the learning sessions. After changing the Library, the IAC must re-run all the indexing processes to include the changes.

Browsing the Theory Library

The IAC or a regional Anthropology Coordinator is the most likely person to browse the SILKin Library of known domain theories, but a field worker might have reason to do so. One possible future enhancement of the Library would be the addition of URL's pointing to web resources with more detailed or scholarly information about each cultural context. If and when that happens, any field worker who uses Suggestions might learn that a proposed definition for their data comes from a Library context they've never heard of. The URLs might point them to information that is more relevant to kinship analysis than a general Google search.

There is nothing to prevent recording informative URLs in a Domain Theory file for potential future use. The format of a domain theory file includes a header item called 'citation.' The content of a 'citation' item is just a string of text of arbitrary length. It currently cites only the source document from which the kinship information was taken, but additional URLs or document citations can be included. There is no size limit.

If in the future you wish to make the citation field visible (and/or editable) in a Library Browser, that will be a simple bit of programming. (The citation field is already visible and editable in the Domain Theory Editor that can be reached via the Context Editor.)

Library Browsing Window

To browse, the user chooses 'Edit > Edit Context' to launch MainPane. (The IAC may not have any context loaded into SILKin; she may choose 'Edit > Admin Mode' to launch MainPane.) Then she chooses 'File > New Library Browser' to launch a browser window (above). Note that more than one browser may be open at a time, to facilitate comparisons. In each browser, she may examine one definition at a time, or may look at only one clause of a definition at a time.

The call stack for library browsing is straightforward:

Browse Library call stack: MainPane:actionPerformed(e="new browser") ->   browseLibrary()  ->   LibBrowser (constructor)  ->  

The actions triggered by the four drop-down menus simply identify the language, kin term, and possibly the particular clauses that should be displayed. They are implemented via ActionListeners that are inner classes in the DisplayPickerPanel class.

The DomThListener fetches the chosen domain theory (e.g. Agta) from memory or disk, and then loads the 'Choose a Kin Term' drop-down menu with the kin terms (terms of reference in this case) for that language. The list will include auxiliary terms as well as standard macro terms like 'brother.'

The KinTermListener fetches the KinTermDef for the chosen term and loads the 'Choose a Clause' and 'Choose an Expansion' drop-down menus. It then displays the basic definition in Horn Clause form in the top display pane (all clauses), and all the expanded clauses in the pane below it.

The ClauseListener restricts the top display pane to only the basic clause chosen by the User unless she chooses 'All.' Likewise the ExpListener restricts the middle pane display to the single expanded clause chosen by the User unless she chooses 'All.' The format of an expanded definition as printed is explained here.

The 3 radio buttons can restrict the displays to only Primary kin terms, only Extended meanings, or both. A few of the Library domain theories recognize extended meanings of certain terms, and SILKin's Horn Clause syntax allows for a flag on a clause designating it as an extended meaning. The syntax of a SILKin Horn Clause also allows for an 'exception' flag, but that has never been used to date.

The bottom scroll pane can display family tree diagrams of any combination of term/base clause(s)/expanded clause(s). Just like the Chart pane in the main SILKin window, it has a drop-down menu that allows for multiple charts to display the current diagram. It also allows the User to edit the label for each chart.

In the main SILKin window, the User draws the charts, deciding where each symbol should go, how many charts to use, and when to use Links between charts. But in the Library Browser, all those decisions must be made algorithmically. This layout task is the same as when we import a GEDCOM file with its population; the GEDCOM specifies the genealogical relationships, but not spatial placements. Therefore, we use methods from the GEDCOM parser to assign the chart location of each symbol. We add the kin terms under each symbol, displaying multiple terms if more than one applies. In languages that have multiple terms for the same relative, making room for all the terms under one symbol can make for very wide spacing on diagrams.

Diagram Examples call stack: LibBrowser.DiagramButtonListener:actionPerformed() ->   TermChooserFrame (constructor)

      Library Browsing Window

You will notice that the 'Diagram Examples' call stack ends with construction of a TermChooserFrame (see above). After this frame has gathered the user's choices re: which terms and clauses to diagram, User will click on 'Done.' The TermChooserFrame will then call the Library browser's 'diagramTerms' method to carry out the diagraming.

Diagram Examples call stack (continued): LibBrowser:diagramTerms(ArrayList<LibBrowser.TermTriple> chosenTerms) ->   reset(0, 0)  ->   makeEgo  ->   ClauseBody unifyVariables  ->   generateExamples  ->   DT_Abstract1 fillInNames  ->   ParserGEDCOM (constructor)  ->   assignChartAndLevel  ->   processChartRows  ->   LibBrowser:computeHorizontalCushions  ->   ParserGEDCOM:expandToGridUnits  ->   LibBrowser:narrowSpaceAroundFamilies  ->   computeDiagramSizes  ->   reloadChartComboBox  ->   LibBrowserChart:repaint  ->   paintTree

The above methods do a minimal job of laying out a family tree chart. Far better algorithms for genealogical chart layout are found elsewhere, but unfortunately none of them provide for displaying kin terms.

Some Library contexts have very long names for kin terms, and some have multiple kin terms that apply to some people. This can lead to very long strings of kin terms under some symbols on the chart, making for an awkwardly wide diagram. The labeling option of 'kin term letters' is a good way to handle this. Therefore, the paintTree method always presents a dialog box giving the user a choice between full kin terms and letters. When 'letter' is chosen, SILKin will print (on the bottom of the first page of each chart) a legend explaining which kin term maps to each letter.

                                Kin Term Labels Dialog Box

After a diagram has been generated, the drop-down menu is used to rename any one chart or to change to another chart.

Diagram Examples call stack (continued): Link:drawSymbol()  ->   Person:drawSymbol()  ->   Family:drawSymbol()  ->   drawLines()  ->   LibBrowser writeLegend()  ->   writeLegend2Col()

The Print Button on the Library Browser will print either the Horn Clauses in the upper and middle panels, or the diagram(s) in the bottom pane. If examples have been diagramed, the Print button launches a dialog box asking what should be printed.

If 'Definitions' is chosen (or if no diagram exists) another dialog lets the user choose to print all definitions or just the current one. If they choose 'All' we simply pop up an explanation of how to view or print a domain theory file. Otherwise, we print the entire current definition, ignoring any restrictions regarding clauses.

           Expanded Definition Print

The display of an expanded definition contains information about its source and attributes. The above example is the 20th expansion of the base definition of this particular kin term. The Horn Clause on the first 3 lines contains only built-in predicates. (Base clauses may contain kin terms from the target language; this makes them much more compact.)
The fourth line reports 5 attributes of this clause that facilitate comparisons to other clauses:

  1. Alter is at Level -2 relative to Ego. Ego is on level 0; her parents are at level 1, her children are at level -1, etc.
  2. There are 8 parent-child links in the diagram of this relationship.
  3. There is one spousal link in the diagram.
  4. There are no 'star-links' i.e. no connections via a User Defined Predicate (UDP). Recall that the name of a UDP must begin with a star (or asterisk).
  5. The 'PC String' for this clause (also known as its signature) is the concatenation of the abbreviations of the genealogical links on the path from Ego to Alter. In this case, Husband, Father, Brother, Son, Daughter, Son concatenates to HuFaBroSoDaSo.

The last 3 lines of this expanded definition clause give the expansion path. The first element of that path (augui:0) indicates that we started with base clause #0 of the kin term augui in the Cogui language's terms of address. That base clause was compact:

augui(Alter, Ego) :- female(Ego), spouse(A, Ego), sibling[aux]04(Alter, A), male(Alter).

Both 'female' and 'spouse' are primitive (built-in) predicates in SILKin, so no expansion was needed. But 'sibling[aux]04' is an auxiliary term defined elsewhere in this kinship system, so it must be expanded into all-primitive form. The second element of the expansion path tells us that we used clause #2 of the definition of sibling[aux]04. That clause is:

sibling[aux]04(Alter, Ego) :- parents(A, Ego), siblings(B, A), lineal[aux]04down(Alter, B).

Both 'parents' and 'siblings' are plural predicates; they are not built-in, but they are common terms that are pre-loaded into each context as part of the 'Standard Macros' package. The third and fourth elements of the expansion path say that we used clause #1 of 'parents' and clause #0 of 'siblings' (which are 'father' and 'brother'). But 'brother' is also a Standard Macro, requiring two expansions documented in the fifth and sixth elements of the expansion path. Obviously, 'lineal[aux]04down' is another auxiliary term that produces the remaining expansions.

It may or may not be useful to an IAC to know precisely how a base clause got expanded into many expanded definition clauses. But SILKin uses that information internally, so we display it in case it might be useful.

Print Horn Clauses call stack: LibBrowser:printButtonActionPerformed()  ->   printHornClauses()  ->   LibBrowserText (constructor)  ->   PrintHornClauses (constructor)  ->   printTheHCs()  ->   PrinterJob:getPrinterJob() [a Java class]  ->   defaultPage()  ->   pageDialog  ->   setPrintable()  ->   printDialog()  ->   print()  ->   PrintHornClauses:print()  ->   Graphics:drawString() [a basic Java class]

If 'Diagrams' is chosen, another dialog lets the user pick 'Current Visible Portion' or 'Current Chart' or 'All Charts.' Then a system-dependent page set-up dialog allows the choice of landscape or portrait mode. (Choosing a scaling percentage unfortunately does not work in Java.)

Finally, a system-dependent print dialog controls the usual printing options

Print Diagram call stack: LibBrowser:printButtonActionPerformed()  ->   printDiagrams()  ->   PrintDiagram (constructor)  ->   printTheChart()  ->   PrinterJob:getPrinterJob() [a Java class]  ->   defaultPage()  ->   pageDialog  ->   setPrintable()  ->   printDialog()  ->   print()  ->   LibBrowser:getComboBoxItems()  ->   LibBrowserChart:paintTree()  ->   Link:drawSymbol()  ->   Person:drawSymbol()  ->   Family:drawSymbol()  ->   drawLines()  ->   LibBrowser writeLegend()  ->   writeLegend2Col()

Updating Library Indexes

One task that should be done only by the IAC (or someone assisting them) is maintaining the Library of known kinship systems. Adding and deleting domain theory files has already been discussed. The IAC must be in Admin mode (from the 'Edit' menu of the main screen) in order for the Admin menu choices to be enabled. There are two scenarios for recomputing the indexes:

There are no options or decisions in the first two steps. The third and fourth steps are more complicated.
In the standard SILKin Library as of 2019 there are 54 domain theories ('.thy' files) for 50 contexts (languages). That is because 4 of the contexts have distinct terms of address; each requires an extra '.thy' file.
To avoid overloading the memory of your computer, it is best to generate indexes for batches of about 13 languages at a time. Assigning sequential suffixes tells SILKin the order in which to merge them in the fourth step. (For the last batch, entering an END number larger than the number of languages is safe; SILKin will stop when it runs out of files.)

In the third and fourth steps, it does not matter what suffixes you use, so long as you enter them the same way in the same order each time. For example, you could use suffixes of B, 13, HI, and bye. So long as you are consistent, SILKin doesn't care.

Updating indexes helps the Learning Module to work more efficiently, but it is not necessary to issue a new version of SILKin (or a new Library) every time one new kinship system is added. But this process should be done before issuing a new release of the software if any new kinship systems have been added.


Continue to the next topic: the Major Data Structures in SILKin.