Symbolic Logic:Programming:Value Set Implementation

The laws describing Value Sets show that when a function is applied to parameters the result is in the cartesian product of the set of input values for the parameters, and that each value in this result set has a Possible World which is the intersection of the Possible Worlds from which the values came.

This Possible World is then empty if,
 * Any of the Possible Worlds in the intersection is empty.
 * Any of the Possible Worlds came from different values in the same Value Set.
 * The resolution rule applies.

The implementation of Value Sets is based on these rules. There is a large overhead with working with Value Sets, and the size of Value Sets may increase exponentially. This needs to be minimized.

Value Sets are not needed in many situations. The implementation of Value Sets is intended for those situations where multiple values may be possible. The CLP should only use Value Sets when they are needed.

The implementation of Value Sets needs to create cartesian products of value sets only when looking at the individual values. Many functions can pass the value sets through to primitive built in functions like + - * /.

Primarily it is the primitive built in operations in the language that need to build cartesian products of value sets.

Design
Primary possible worlds are constructed for every value for a variable. These Possible Worlds are the parent worlds out of which the other worlds are constructed. They have an identity, and may be empty or not empty.

When a Cartesian product of value sets is constructed Possible Worlds are created from the intersection of Possible Worlds from the source Value Sets. These are secondary Possible Worlds.

A secondary Possible World is always the intersection of a set of possible worlds. A secondary Possible World object has a list of Possible Worlds, called the parent Possible Worlds. A secondary Possible World is the intersection of its parents.

A primary Possible World has an empty list of parent Possible Worlds.

Checking for duplicate Secondary Possible Worlds
The full list of primary Possible Worlds obtained from recursively visiting all the parents of a Secondary Possible World identifies it. Before constructing a new Possible World out of a set of we need to check that no Secondary Possible World object exists representing the same Possible World.

When we need a world which has a particular set of primary worlds a Hash Map is used to check if a Secondary Possible World object has already been created. The key for this look up is the sorted list of Primary Worlds. A hash function would be formed from this list.

Testing a Possible World
To test if a Possible World is empty first traverse the Parent Possible Worlds recursively looking for an empty possible world.

If an empty possible world is found mark all its children as empty.

Testing for distinct Parent Possible Worlds from the same World Set
Logically a list of tuples is constructed by traversing the parent possible worlds and there worlds sets to form a list of tuples,
 * (World Set, {set of possible worlds}]

If any World Set has more than one possible world then the original Possible World is empty. This is because the intersection of Possible Worlds from the same World Set is empty.

An efficient (but not parrallelizable) algorithm
Boolean flags are required for use in testing for Possible Worlds from different values in the same Value Set.
 * A flag on the Value Set.
 * A flag on the Possible World.

These flags must be cleared at the start of a test.

To test if any two Possible Worlds are from the same Value Set.
 * Starting from an original Possible World.
 * Traverse the Possible Worlds and there Value Sets.
 * Clear the marks.
 * Traverse the Possible Worlds and there Value Sets.
 * If the Possible World is not marked.
 * Check for the Value Set already marked.
 * Then a Possible World from another value in the value set must be in the intersection.
 * The original Possible World must be empty.

Note that only one such test may be performed at a time. If there were multile threads, both performing the same test, the marks would get mixed up, giving wrong results.

Memory Management for Possible Worlds
Use reference counting for memory management on the Possible Worlds. Because all the references are from the child to the parent there are no reference cycles so reference counting is a satisfactory strategy.

Completeness
Completeness refers to the ability of the implementation of the Value Set implementation to always detect when a Posible World is empty.

Value Sets are equivalent to sets. The rules for calculation Value Sets from functions of Value Sets allow any expression to be evaluated.

These calculations give expressions for Value Sets. These expressions form the basis for the representaion of Possible Worlds.

To prove completeness it is necessary to show the equivalence of the representation of a Possible World with the mathematical rules that describe a Possible World.

TO DO - Complete this proof.

Code
This is design code only at this stage. Not guaranteed correct.

This code is in C++.

A Possible World may be represented as an object with,
 * A boolean representing the existance of the possible world (not empty).
 * A boolean used for "marking" when detecting Possible Worlds from the same Value Set.
 * A list of component Possible Worlds.
 * A pointer to the value set.
 * A reference count.

Statics
Find an existing world with the same set of primary Possible Worlds. Two worlds with the same set of primaries are the same world. So find the existing world, or if not found then create it.

Primary Worlds
The list of primary worlds uniquely identifies the Possible World.

Parent possible worlds
Methods for managing the list of Possible Worlds. This world is the intersection of the list of Possible Worlds.

Test for empty possible worlds
When a calculated value does not match the expected result, the boolean value is set to false to represent the Possible World being empty. The tagged value is removed from the Value Set.

Because the Possible Worlds from a Value Set form a partition, the intersection of two Possible Worlds from the same Value Set must be empty.

Resolution
Resolution detects values that are incompatible with another source Value Set, using the Resolution Rule. The process starts with a source Value Set.

Mark all the Possible Worlds related to this Possible World. This is used in marking all the Possible Worlds in a Value Set.

Detect all values in the Value Set whose Possible Worlds are not represented in the source Value Set. The Possible Worlds for these values must be empty by the Resolution Rule. Clear all the marks afterwards.

Called for every value in the Value Set. If this World is not marked then it is not present in the source Value Set, and must be empty by the Resolution Rule.

Private Methods
Clear all the marks. But while we are doing it check for any empty worlds.

Use the marks to check if Possible Worlds from two values in Value Sets are in the intersection. Because the Possible Worlds from a Value Set form a partition, the intersection of two Possible Worlds from the same Value Set must be empty.

Get the Value Set to update its list of values now that this world is empty.

Variables
Function calls that cannot immediately be evaluated may need to be delayed. Other calls to functions may need to wait for the variable to be calculated. A variable records if the value is ready yet, and receives the value, once it is calculated.

A variable may need to store one value (the Value class), or multiple values (the Value Set class).

A basic set of types is listed in the table below.

#define v1 Value #define vs ValueSet

Cardinality
The cardinality is the number of values it may have.
 * Value
 * Known - 1
 * Unknown - cardinality of the type.
 * Value Set.
 * Known - Sum of the cardinality for each tagged value.
 * Unknown - cardinality of the type.

Cardinality can get very large. When the value is very large we only need to know that it is large.

Reference class
This is just an implementation detail. The reference class allows the basic types, bool, long, and double to be used along with pointers to objects. Template specialisation is used to hide the difference. Used in the Value and Tagged Value classes. This is so we can write, vs and vs.

Value class
The Value class is used to represent a variable that may only have one value. The implementation uses the Reference class to hide the difference between the basic types bool, long, double and pointers to objects. The Ref class has the member variable m_Value.

Value Set class
The Value Set class is used to represent a variable that may have mulitple values.

A Value Set class must efficiently implement a set of (value, Possible World) pairs. There is the potential for much memory to be used on storing these lists. Many alternatives were considered. In the end a simple vector approach seems to be the cleanest.

A Value Set is a vector of Tagged Values. Each Value has,
 * Value
 * Possible World
 * Matched flag.

Value Sets must be pruned of unmatched values when unified with other Value Sets. A flag is recorded on the Tagged Value class for this purpose.

Test for Possible Worlds from different values in the same Value Set
If a Possible World is the intersection of different worlds from two different worlds with the same Value Set, it is empty. To test for Possible Worlds from different values, mark the Value Set and the Possible World. Before marking test if the Value Set is marked but not the Possible World. In this case a Possible World from a different value has been detected.

Resolution
Resolution detects values that are incompatible with another source Value Set, using the Resolution Rule. If a Value Set was used in calculating this Value Set, all the Possible Worlds must be represented in this Value Set or be empty.

The test is performed by marking all the Possible Worlds referenced by this Value Set, and then checking the other Value Sets for unmarked values.

Utilities
Remove values with empty Possible Worlds.

Tagged Value class
A TaggedValue is a tuple of a typed value and a Possible World. The implementation uses the Reference class to hide the difference between the basic types bool, long, double and pointers to objects. The Ref class has the member variable m_Value.

Design
The most general situation is where the inputs and outputs all have Value Sets with one or more values. In this case create a new Value Set from two of the Value sets and merge it with the third. Resolution is then applied to remove values from Value Sets who's Possible World has been shown to be empty.

This is code below using C++ template classes (for binary operations). It may be better to generate all this code. In any case this code describes the algorithm.

To represent any operation start with a class representing the method that implements that operation, in the Call method on 3 parameters that are not value sets.

The class Cartesian has a template parameter for the operation class. It inherits the Call method from the operation class and then extends it with overridden Call methods with various combinations of Value Set parameters.

Code
To multiply 2 values A and B to obtain a third,

A, B, and C may or may not be Value Sets. So there are many possibilities.

Binary Operator Classes
A small class is is used to capture the operation. A class like this would be written for each built in operator.

Logical Binary Operators
The inverse logical operators require a Value Set as the result. This is the problem with logical operators that is solved by using Value Sets.

Cartesian Class
Here is the implementation of the Call function with 3 parameters. The implementations with 1 and 3 parameters are similar.

Construct the output out of the cartesian product of the inputs. This is a simple version. Other versions are given below, using ForEach,

Actions
The above implementation has a for loop for the result when maybe none is necessary. Only basic functions that return a result set need this. We would like to use the ForEach method, and have it use a for loop for a ValueSet, and not for a Value.

At times like this I miss closures. I dont really want to code multiple versions of the Cart2 function. It would aggrevate my RSI. If C++ had closures, I could write.

Alas C++ does not have closures. It has template classes. The parameters cant just be picked up from the enclosing scope, but we can construct classes to act like functions with extra parameters.

The Value Set class has the method,

template  ForEach(A &a)

This method calls the Process method on A passing the value and the possible world. Apologies for what follows. Its not as nice as I would like.

Somewhere to save the results passed into Process.

A class for the outer ForEach call (on the parameter A).

A class for the next loop call (on the parameter B). Note that classes ActionCall1 and ActionCall3 are defined similarly.

A class for the outer loop call to take the values from the result and put them in the parameter C. Note that classes ActionResult1 and ActionResult3 are defined similarly.

Cartesian Product Functions
The new definition of the Cart1 function.

The new definition of the Cart2 function.

The new definition of the Cart3 function.

etcetera

UnifyRight
When comparing two Value Sets it is faster to sort the Value Sets and then merge the results.

The time to calculate the cartesian product of two value sets is proportional to the product of the number of entries. By first sorting the Value Sets by value, the values sets can be merged in linear time. However the sort takes n log n time.

The resulting merge still needs to create a cartesian product when multiple entries have the same value.

Links

 * Symbolic Logic:Programming:Value Set Programming
 * Symbolic Logic:Programming
 * Intelligence and Reasoning