Scasp: knowledge representation

Hi @JasonMorris,

In your notebook you seem to have a methodical way to represent human knowledge with compound terms. Could you please write about the rules of thumb or method you commonly use?

1 Like

Hey, @swi.

I assume you must be talking about the way that the notebook approaches defeasibility. Defeasibility is the idea that rules can be expressed as exceptions to other rules.

The standard way of representing it would be the typical “a bird flies unless it is a penguin”, which is often generalized as “a bird flies unless there is a reason it doesn’t”.

flies(X) :- bird(X), not non_flying_bird(X).
non_flying_bird(X) :- penguin(X).
non_flying_bird(X) :- emu(X).

The problem, for legal purposes, is that the exceptions are not usually expressed in the general rule in that way. If your natural langauge law reads like this:

  1. Birds fly.
  2. Despite section 1, penguins do not fly.

Then the information about the defeasibility is not encoded in section 1, it is encoded in section 2. Being able to respect the legislation’s choices about where the defeasibility information goes increases the structural isomorphism of the code to the original law, making the code easier to write, and in particular making the code easier to maintain when the law is amended in ways that adds new exceptions.

So to address that problem, I used an approach similar to the one that is taken in Flora-2 (aka ErgoLite), called “logic programming with defaults and argumentation theories”. In Flora-2 it is implemented using higher-order logic features. I implemented it in s(CASP) using compound terms, which is perhaps less elegant.

The way it works is that if a conclusion of a rule is defeasible, then you wrap the conclusion of the rule in the according_to/2 predicate. Then, when you are checking to see if another conclusion has been reached, you can ask whether it has been reached by any rule, or whether it has been reached by a specific rule, or which rules have reached that conclusion.

This has the added advantage that you can use the “according_to” predicate in the natural language explanations as a place to put information about the section of the law that was used to reach the conclusion. Unfortunately, because s(CASP) can’t recognize compound terms in the #pred statements, this is still quite unwieldly, which is why in my encoding I simply report the symbol name for the rule instead of trying to provide any context specific per rule.

So you use according_to/2 to say that something was defeasibly concluded, or to check for that fact.

Then you can use opposes/4 to say that a conclusion by one rule is contradictory to a conclusion by another rule. You can also use overrides/4 to specify which rule and conclusion should override the other rule and conclusion.

This can also be used to specify that rules cannot be overridden, I think, by saying things like
-overrides(_,_,strict_rule,_).

The argumentation theory is then used to provide a legally_holds/2 predicate, which indicates that a conclusion by a rule is valid according to the argumentation theory implemented. The argumentation theory implemented is similar to Flora-2’s default, but is probably implemented slightly differently, I’m not sure. To simplify, it says that if a rule’s conclusion opposes and is overridden by another rule’s conclusion, then the first rule’s conclusion does not legally hold, and the second rule’s conclusion does legally hold. It gets more complicated when you consider whether the defeating rule was defeated first, and whether a rule cyclically defeats itself by closure, what happens if there are rules that disagree but there is no priority between them, etc.

Using that approach, you can put the information about the defeasibility relationships wherever they “belong” in the code. So you can re-implement the above code as

% Section 1
according_to(section1,flies(X)) :- bird(X).

% Section 2
% encodes "penguins do not fly"
according_to(section2,-flies(X)) :- penguin(X).
% encodes "despite section 1" 
opposes(section1,flies(X),section2,-flies(X)).
overrides(section2,-flies(X),section1,flies(X)). 

Now my code has one piece for section 1, and another piece for section 2. The information in section 1 of the law is reflected in section 1 of the code. The information in section 2 of the law is reflected in section 2 of the code. If anyone adds a section to the law later, that overrides anything else, or is overridden by anything else, I will only need to add a corresponding section of code, without needing to worry about modifying the rules that it defeats or is defeated by.

So you can see that I’m using compound terms in the second parameter to according_to and legally_holds, and in the second and fourth parameter of opposes and overrides.

The downsides are several. If you query flies(X), you will get nothing. It multiplies the number of #pred statements you need to use if you want clean explanations. If you use this approach for defeasibility across your entire legislation, it becomes very slow, particularly with multiple rules that unify with either of the relevant conclusions, because now it needs to know what every section of the law concludes before it can determine if the one section you are interested in holds.

With sufficiently complicated rules the method may collapse under its own weight, I don’t know.

The upside is that you maintain structural isomorphism with the law, and if you query legally_holds(Rule,flies(tweety)). you will get an answer, and an explanation that checks to see whether any other Rule concluded otherwise. It also gives you a way to test whether the defeasibility relationships in the law were successfully encoded in the law, because now you can write queries like “is it true that tweety does not fly despite being a bird because they are a penguin?” (e.g. ?- according_to(section1,flies(tweety)), according_to(section2,-flies(tweety)), not legally_holds(section1,flies(tweety)), legally_holds(section2,-flies(tweety)).)

I also don’t know if it is compatible with other techniques that also use compound terms, like the Event Calculus examples in the Ciao implementation of s(CASP). I suspect something along the lines of

according_to(section1,initiates(citizen(X,uk),T)) :- occurs(birth(X),T), value_at(location(X,uk),T).

may become problematic.

Let me know if that answers your question, or if I missed the mark. :slight_smile:

4 Likes

Yes, this is quite helpful, thank you for your explanation. :slight_smile:

I am seeking to gather the meta-process you used (it is embedded in your personal experience, I think) to encode the legal text. From your explanation I gather this first major conclusion:

  1. You first establish the meta-model that applies to the knowledge domain. In this case, for you, it means to establish the different relationships between the main objects in the domain: rules, facts, conclusions. This you did by declaring predicates about legal rules (overrides, according_to, opposes, legally_holds, etc.).

Now that you have this framework you concentrated on applying the framework. For example, I would like to ask you about the process you used to produce this encoding:

according_to(r34_1_d,described_in_s1(X)) :- 
   involves_sharing_fees(X,Fees,Recipient),
   as_compensation_for(Fees,Work), 
   performed_by(Work,Lawyer), 
   legal_work(Work), 
   unauthorized(Recipient).

The natural language wording was:

(d)	any business which involves the sharing of the legal practitioner’s fees with,
or the payment of a commission to, any unauthorised person for legal work performed
by the legal practitioner;

Did you use any specific method to move from the natural language to the encoding? What rules of thumb did you use to decide when to make a new predicate? or to decide its arguments?

1 Like

Ah. Fascinating!

The process is not formalized in any way. I don’t have a method that I could describe.

I guess the basic concept is that you have a conclusion, and a condition. The condition could be a single boolean predicate. But then it’s gets broken down into parts. And at some point you decide that’s enough parts. So the question of how to model it, in my mind, is sort of like the question of “why would breaking this condition down into smaller pieces make sense”, or “why wouldn’t it,” and then the same question applied to each of the pieces you create, until you have a list of conditions it doesn’t make sense to deconstruct any further.

If I’m looking at that section of code and asking why I did it that way rather than any other way, there are a few reasons. First “does this business involve the sharing of fees” seemed like a more tractable question to put to a user than “does this business involve the sharing of fees or the payment of a commission”. It also seemed like the sort of information that would be within the knowledge of the user, and did not ask the user to draw a legal conclusion, but rather just report a fact.

The legal practitioner is an object that is being used repeatedly in many rules, so having a predicate that connected that existing concept to the “work” they performed seemed reasonable. Now that we have a “work” object, the things we need to know are whether it was legal work, and whether the money received was in compensation. Whether they were fees or commission was not something about the work, so it made more sense to separate out involves_sharing_fees and involves_commission into two different versions of this rule.

Adding unauthorized(Recipient) made sense because it was a question the user would be reasonably expected to have the answer to, it simplified the other questions, and “people” were a list of objects that we were going to be collecting from the user in the user interface, anyway, so adding whether each “person” is “authorized to provide legal services”, when that question is relevant, was convenient.

Modeling the sharing of fees as a ternary predicate, between the business, the fees, and the recipient, was done because making it binary would have required creating an entity “the fee sharing” that was associated with the recipient and the business, for example, and that would be an uncomfortable thing to ask the user to name, or give a symbol for. So I anticipated that “Fees” here was always going to be a symbol provided by the user-facing app. The app would ask “does this business share fees with anyone”, and the “this” would be the first parameter, some arbitrary symbol would be the second, and the person selected would be the third. That is sufficient, because it doesn’t actually matter what the fees are, merely that they exist, and were in compensation for work. The identity of the fees is irrelevant to any question you might want to ask.

What I can’t really explain, right now, is why this rule doesn’t include business(X), legal_practitioner(Lawyer). Looking at it again, I think their exclusion is an error. Because r34_1_d would not reach those conclusions about non-lawyers, and non-businesses. But it may have been left out for speed considerations, I’m not sure?

Another motivation that was involved, and that ought to have pursuaded me to include business(X), legal_practitioner(Lawyer), was that the purpose of the encoding was, in part, to detect drafting problems in the original rule. So maintaining fidelity to the words that are re-used in the law, and making each of those represented by a predicate that is re-used in the code, increases your ability to detect issues with how those words are being used. In legislative drafting, consistent use of terminology is one of the highest principles, so being able to test for that is valuable.

In fact, it was mis-use of the word “business” in r34(1)(b) that caused us to be able to detect a drafting problem, and recommend the amendment included in this notebook. So for those purposes, the two extra clauses should be there, I think.

So the considerations are mostly practical:

  1. What do you need to model (how deep to you need to go) in order to achieve your objective.
  2. What is the user likely to know? What question are they likely to understand?
  3. What is a reasonable structure in which to pose questions to the user? How can that structure reflect the relevant entities and relationships?
  4. What are the limits on the scope of what you’re trying to encode?

Thank you for the very interesting question. It’s a topic I’ve been wanting to set my mind to for a while, and answering it has clarified some of my thoughts.

5 Likes

This is very useful, I deeply appreciate you taking the time to answer.

It it very interesting that you tightly connect your modeling method with the human user (and not so much to the software that will use the model) both in terms of what the user knows, what questions they can answer, which ones they can’t, and also what problems they need to solve (e.g. drafting laws). Very good properties for a model I would think.

Part of the reason for that connection to the user is because we were anticipating using a tool like L4-Docassemble that allows an automation between a data structure and a user interface, so there is a strong relationship between predicates, and questions that will be posed.

If you’re using a different toolset, for instance if you are using ontologies as a data structure middle-man, then the motivations change. Then you are motivated to match your code to the ontology, because the ontology has a way of exposing itself to users. So it’s very context dependent.

1 Like