A NetLogo case based reasoning toolkit, cbr
as a NetLogo plugin.
See the install document
See the build document
- BUILD.md - the build instructions
- INSTALL.md - how to install the plug-in in NetLogo
- bin - directory where all the scripts and compiled classes are stored. Be careful some IDEs automatically clear this directory on a clean - there are useful scripts that are not automatically generated present in this directory.
- doc - documentation directory. Contains documentation. Be careful some IDEs automatically clear this directory on a clean - there are useful files that are not automatically generated present in this directory.
- INSTALL.md - instructions on how to install the plug-in.
- jar-manifest.txt - manifest used to create the cbr.jar - the plugin.
- lib - required libraries - these are already present in NetLogo.
- LICENSE.txt - How you may use this plugin.
- MANUAL.md
- paper - A paper describing the plugin.
- PUBLISHING.md - the instructions on how to release for use as a published NetLogo extension.
- README.md - this file.
- src - The source code for the plugin. Normally this would be something like
src\uk\ac\hutton\cbr
or whatever. The problem with this is that the plugin extension for NetLogo makes it difficult to adopt this structure, therefore is just bunged insrc
where all classes share the default package space. - test - A test NetLogo model. Normally we would used automated testing. The
problem with this is setting up things like the Context is rather involved in
NetLogo, so we use a NetLogo model to test the plug-in. There is a README.md
in this directory with instructions on how to do this. Also the actual plugin
jar
cbr.jar
is in a subdirectorycbr
, which is the jar you need to install the plugin. - TODO - list of outstanding tasks to complete on the plugin.
- tutorial - a presentation giving a brief explanation of how to use the NetLogo extension
- UPGRADING.md - instructions on what to change if you should have to upgrade the version of NetLogo.
- CITATION.cff - used by Zenodo to generate the citation required for this software.
See also the manual
This is an example of how to use the case based reasoning plugin.
The code implements a reference version of code using the extension that is described here.
Select the run-testing button to make sure that the code is behaving as it should. If there is no error dialogue then the code is working as expected. The code also shows how to the use the extension.
Normally a case base consists of a series of cases, each of these cases consist of
- state
- decision/activity
- outcome
The state can be anything such as the bank balance of the agent. The decision/activity might be to install central heating. The outcome might be straight forwardly yes or no. It might be probability, or it might some arbitrary decision/activity metric for use elsewhere.
A state and decision/activity are presented to the case base. The case base is searched for the closest match (if there is one) and the outcome of that match is given.
A NetLogo case consists of a state in any of the standard Netlogo variables,
such as list, number, string, etc. This is strictly defined by the cbr:lambda
which is the comparator program used to determine the "distance" the three
cases:
- case A
- case B
- referent case R
are relative to ecah other. That is, if:
- the case A is 'closer' to the referent case R than the case B using
cbr:lamda
to the referent case R thencbr:lt
is returned - the case B is 'closer' to the referent case R than the case A using
cbr:lamda
to the referent case R thencbr:gt
is returned - the case B is 'same distance' to the referent case R than the case A using
cbr:lamda
to the referent case R thencbr:eq
is returned - the case B is 'closer' using
cbr:lamda
to the referent case R thencbr:lt
is returned
The cbr:lamda
is then used by cbr:match
or cbr:matches
to determine a
single closest match of a "hypothetical" case that is presented to cbr:match
or more or one equally close matches if cbr:matches
is used. The case will
consists of a state and decison and returning the 'best match' base on the
repeated use of cbr:lamda
to determine which is the closest match. If the
match does not meet a suitable criterion then cbr:incmp
is returned.
When using cbr:match
, then if there are more than one match then the case
with the highest rank is returned, or failing that the oldest. If both these
conditions are equal then the result is a random selection from among those
cases. More than one case may be returned if cbr:matches
is used.
If no cbr:lamba
is provided then a default lambda is used. The specification
for this is as follows.
The comparison algorithm always has three arguments:
- Case A
- Case B
- Referent case R
Cases A and B need not be in a case base; they may also have empty Decision and Outcome parts, or simply be a State. Case R may have empty Decision and Outcome parts, or simply be a State. (In a typical use-case, R is an ‘expected state of the world’ for which the deciding agent is trying to find a range of cases.)
The algorithm returns one of four responses:
cbr:gt
– iff A more similar to R than Bcbr:lt
– iff B more similar to R than Bcbr:eq
– iff A and B are equally similar to Rcbr:incmp
– iff A and B are not comparably similar to R
The primitives available are:
Instantiates a case base object.
None
- case base object
Adds a case the the NetLogo case base object. The tick at which this takes
place is also recorded and can be accessed using cbr:get-time
. This property
can be manipulated by usingcbr:set-time
Note the case object cannot exist independently of the case base object. If the latter is destroyed or removed, then so to is the former.
-
case base object
-
state (in a form that
cbr:lamda
requires for doing the "distance" function. -
decision/activity (in a form that
cbr:lamda
requires for doing the "distance" function. -
outcome (in any form that you require, providing this is not used as part of the decison making in
cbr:lamda
).
Returns a case object. This is not really useful, but can be used for display and debugging uses if needs be.
All the cases from the second case base are appended to the case base in the first argument
- a to case base object
- a from case base oject
Nothing.
Used to return all the cases in a specific case base.
- case base object
A NetLogo list of all the cases in the case base object.
Removes a particular case from a case base.
-
case base object
-
case object (returned when the case is created)
Nothing.
This is a reporter on the state contained within a case object.
-
case base object
-
case object
A state in the form the cbr:lamda
uses. In the example code showed above this
takes the form of a list of strings.
This is a reporter on the decision/activity contained within a case object.
-
case base object
-
case object
A decision in the form the cbr:lamda
uses. In the example code showed above this takes the form of a string.
This is a reporter on the outcome of a case.
-
case base object
-
case object
In the example code showed above this takes the form of a list of booleans.
Time is used in conjunction cbr:forget
to remove aged cases from the case
base. The time is 0 or a postive number and corresponds to the number of ticks.
-
case base object
-
case object (returned when the case is created)
-
number of ticks representing when the case was entered into the case base table.
Nothing.
Obtains/gets the time of origin on a particular case within a case base object.
This is measured in number of ticks, is 0 or positive. Time is used in
conjunction cbr:forget
to remove aged cases from the case base.
-
case base object
-
case object within the above case base
A number of ticks, representing when the case was added to the case base. This
is generally the current cclick but may be manipulated using cbr:set-time
.
This sets the earliest tick. If a case base has a creation time (as a tick)
older than this, then upon the evocaction of the next cbr:forget
then this
case will be deleted, i.e. forgotten.
-
case base object
-
number representing the oldest tick, before which if any case is stamped with an earlier time then these cases will be removed upon invocation of
cbr:forget
. The number in this case corresponds to the number of ticks.
Nothing.
If the cbr:set-earliest
has been used, then cbr:forget
will enforce this
limit. We have done it this way to prevent exceptions being thrown if the this
limit is exceeded, thus it requires explicit invocation via the user to make
the case base forget.
A tick is used to denote time. A NetLogo case base will have as part of its
metadata the time before which all cases acquired at that time will be
forgotten. This property may be obtained using the cbr:get-earliest
and by
default is set to 0.
The actual age of a case within a casebase can be manipulated with the use of
cbr:set-time
and cbr:get-time
.
This may the reporter that does distance comparison. This is never called
directly If a cbr:lambda
is not provided, then the default comparator is
called. The default comparator has the following specification.
The algorithm should do the following:
-
If R is a case, then set R to the State part of the case
-
If A is a case, then set A to the State part of the case
-
If B is a case, then set B to the State part of the case
-
We now consider different datatypes for R, A and B in S = { number } and M = { string, patch, turtle, link, list, agent set } a N. B. S is a set of scalar datatypes; M is a set of multidimensional datatypes
-
If R, A and B are not the same datatype, return
cbr:incmp
-
If R, A and B are all numbers, then if abs (R – A) < abs(R – B) return
cbr:gt
; else if abs (R – A) > abs(R – B) returncbr:lt
; else returncbr:eq
-
If R, A and B are all agent sets, then let Am = A – (A intersect B) and let Bm = B – (A intersect B). If Am and Bm are both empty, return
cbr:eq
. Let AmR = Am – (Am intersect R); Let BmR = Bm – (Bm intersect R); Let AR = A intersect R; Let BR = B intersect R. If AR is a superset of BR and AmR is a subset of BmR, returncbr:gt
; Else if BR is a superset of AR and BmR is a subset of AmR, returncbr:lt
; Else returncbr:incmp
Basic principle: A is more similar to R than B is if all agents that B has in common with R, A also has in common with R, and all agents that A does not have in common with R, B also does not have in common with R. If there are members of R that are members of A but not B, and there are also members of R that are members of B but not A, then A and B are incomparably similar to R; likewise, if we look at members of A and B that are not members of R, unless one is a subset-of-or-equal-to the other, A and B are incomparably similar. The diagram below shows the three conditions in which the agent set comparisons are not incomparable. Note that in the left one, if B – (A intersect B) is empty, A is still more similar than B to R; also, if A – (A intersect B) is empty, A is still more similar to R. If both those regions are empty, then A and B are equal. The corresponding points are true of the middle diagram (i.e. with A and B swapped). -
If A, B and R are all lists, then let answer =
cbr:eq
. For i in 1:min(length of A, length of B, length of R): if A[i] == R[i] and B[i] != R[i], then if answer ==cbr:lt
, returncbr:incmp
; else let answer =cbr:gt
. Else if B[i] == R[i] and A[i] != R[i], then if answer ==cbr:gt
, returncbr:incmp
; else let answer =cbr:lt
. End For. If answer ==cbr:gt
and A has the shortest length and and For all j in (i + 1):min(length of B, length of R), B[j] != R[j], then returncbr:gt
. If answer ==cbr:lt
and B has the shortest length and For all j in (i + 1):min(length of A, length of R), A[j] != R[j], then returncbr:lt
. If A and B have different lengths, returncbr:incmp
. If For all j in (i + 1):(length of A), A[j] == B[j], then returncbr:eq
ual else returncbr:incmp
Basic principle: A is more similar-to R than B if everywhere that B is equal to R, A is also equal to R -
If A, B and R all being strings can work on the same basis as lists, with i and j indexing characters.
-
If A, B and R are all patches then they can also be handled similarly to lists – each of the default and declared patches-own variables would be put into the lists in the same order.
This is the comparator used in the test program to which this documentation is attached.
-
case base object
-
An anonymous reporter or named reporter, which must take four parameters
- case base object
- case A
- case B
- referent case R
This must return one of the following
cbr:lt
cbr:gt
cbr:incmp
cbr:eq
The current represantion that cbr:lamda
must return if of the three cases
- case A;
- case B, and
- referent case R,
when the case A and the case B "are an equal distance" from the referent case R.
This is used to standardise response from cbr:lamda
so cbr:match
and
cbr:matches
will work correctly with it.
The current represantion that cbr:lamda
must return if of the three cases
- case A;
- case B, and
- referent case R,
when the case A is "equal" distance from the referent case R as the case B.
This is used to standardise response from cbr:lamda
so cbr:match
and
cbr:matches
will work correctly with it.
None
The current represantion that cbr:lamda
must return if of the three cases
-
case A;
-
case B, and
-
referent case R,
-
case A;
-
case B, and
-
referent case R,
when the case A is "further" from the referent case R than the case B.
This is used to standardise response from cbr:lamda
so cbr:match
will work correctly with it.
None
The current represantion that cbr:lamda
must return if of the three cases
- case A;
- case B, and
- referent case R,
when the case A is "closer" to the referent case R than the case B.
- None
The current represantion that cbr:lamda
must return if any of the three
cases
- case A;
- case B, and
- referent case R,
cannot be used for comparison.
This gets the earliest time at which a case in a casebase will have been
created at. Before this tick if cbr:forget
is called then all cases with the
casebase that have a time stamp (when they were notionally create) less than
this value, then these cases will be deleted from the case base.
- case base object
- A number which is the maxiumum number of cases the case base can contain.
This gets the earliest time at which a case in a casebase will have been
created at. Before this tick if cbr:forget
is called then all cases with the
casebase that have a time stamp (when they were notionally create) less than
this value, then these cases will be deleted from the case base.
-
case base object
-
A number representing a tick.
Nothing.
This gets the maxium size for the case base. That is the maxiumum number of
cases it may contain. The case base may actually contain more cases than this
number and it requires the specific invocation of cbr:resize
to enforce this
size restriction. Upon the invocation of cbr:resize
those case with the
lowest rank will be deleted. If there are two cases with equal rank then the
oldest will then be deleted.
-
case base object
-
A number which is the maxiumum number of cases the case base can contain.
Nothing.
This sets the maxium size for the case base. That is the maxiumum number of
cases it may contain. The case base may actually contain more cases than this
number and it requires the specific invocation of cbr:resize
to enforce this
size restriction. Upon the invocation of cbr:resize
those case with the
lowest rank will be deleted. If there are two cases with equal rank then the
oldest will then be deleted.
-
case base object
-
A number which is the maxiumum number of cases the case base can contain.
Nothing.
This sets the rank on a particular case in a case base. When choosing between two cases when decreasing the size of a case base to a specified maximum number of cases, then to determine which cases should be deleted to reduce the size of the case base, we use the concept of the rank. All cases start with 0 rank. However, this rank can be set using this primitive, and any case with a higher rank will be preferred when retaining cases.
-
case base object
-
a number representing the rank of the case. The higher the rank, the more likely it is that the case will survive.
Nothing.
This gets the rank on a particular case in a case base. When choosing between two cases when decreasing the size of a case base to a specified maximum number of cases, then to determine which cases should be deleted to reduce the size of the case base, we use the concept of the rank. All cases start with 0 rank. However, this rank can be set using this primitive, and any case with a higher rank will be preferred when retaining cases.
- case base object
Nothing.
This resizes the case base to the number of cases specified by
cbr:set-max-size
. When choosing between two cases when decreasing the size of
a case base to a specified maximum number of cases, then to determine which
cases should be deleted to reduce the size of the case base, we use the concept
of the rank. All cases start with 0 rank. However, this rank can be set using
this primitive, and any case with a higher rank will be preferred when
retaining cases.
This command is explicit. It has been done this way for two reasons. Firstly
this gives control to the user and secondly the only way to interrupt the work
flow in NetLogo appears to be some kind of exception. This, of course means
that the number of cases in a case base can exceed this number. Again this is
intentional and the number can be checked using the code length cbr:all some-case-base
.
- case base object
Nothing.
This returns the closet matched case that matches the state in the state parameter and the decision/activity that has been selected.
-
case base object
-
state in a format that can be use by
cbr:lambda
to do the distance assessment between cases, which describes the necessary state of the agent) -
decision/activity in a format that can be use by
cbr:lambda
to do the distance assessment between cases, which describes the decision or activity that is the agent is about to make or perform).
Nobody if there is no match otherwise returns a match case from the case base, which then may be interrogated using
cbr:state
cbr:decision
cbr:outcome
The first two can be used to validate the case the last is usually what we are after in terms of makeing a decision.
This will return only one case based on the selection criteria mentioned in the "HOW IT WORKS" section above.
This returns the set of closest matched cases that match the state in the state parameter and the decision/activity that has been selected.
-
case base object
-
state in a format that can be use by
cbr:lambda
to do the distance assessment between cases, which describes the necessary state of the agent) -
decision/activity in a format that can be use by
cbr:lambda
to do the distance assessment between cases, which describes the decision or activity that is the agent is about to make or perform).
Nobody if there is no match otherwise returns a list of cases from the case base, which then may be interrogated using
cbr:state
cbr:decision
cbr:outcome
The first two can be used to validate the case the last is usually what we are after in terms of makeing a decision.
(suggested things for the user to notice while running the model)
The typical workflow is
- instantiate a case base using
cbr:new
- add some cases to it using
cbr:add
- query the case base using either
cbr:match
orcbr:matchess
to return one or more cases - Interrogate the returned case outcome to see what the case base recommends.
In addition to this you might to add the resultant state, decision and outcome back into the case base in order to emulate memory.
As mentioned above you can write your own default comparator. Here is an example of how to do this.
to-report new-comparator [some-case-base yes-case no-case reference-case]
let yes-state cbr:state some-case-base yes-case
let no-state cbr:state some-case-base no-case
let reference-state cbr:state some-case-base reference-case
if (cbr:decision some-case-base yes-case != cbr:decision some-case-base no-case and
cbr:decision some-case-base yes-case != cbr:decision some-case-base reference-case) [
report cbr:incmp
]
if not is-list? yes-state or not is-list? no-state or not is-list? reference-state [
report cbr:incmp
]
if length yes-state != length no-state [
report cbr:incmp
]
if length no-state != length reference-state [
report cbr:incmp
]
if (item 0 yes-state = item 0 no-state and
item 1 yes-state = item 1 no-state and
item 2 yes-state = item 2 no-state) [
report cbr:eq
]
let comparison-1-and-3 0
foreach n-values (length yes-state) [i -> i] [ i ->
if item i yes-state = item i reference-state [
set comparison-1-and-3 comparison-1-and-3 + 1
]
]
let comparison-2-and-3 0
foreach n-values (length no-state) [i -> i] [ i ->
if item i no-state = item i reference-state [
set comparison-2-and-3 comparison-2-and-3 + 1
]
]
if comparison-1-and-3 = 0 and comparison-2-and-3 = 0 [
report cbr:incmp
]
if comparison-1-and-3 > comparison-2-and-3 [
report cbr:lt
]
report cbr:gt
end
This example makes sure the decision is the same in all the matched cases as well. The defautl comparator does not do this.
Once this is implemented then it can loaded against a particular case base by using the command:
cbr:lambda new-comparator
(interesting or unusual features of NetLogo that the model uses, particularly in the Code tab; or where workarounds were needed for missing features)
achsium.nlogo uses this case base reasoner.
Author [email protected] (see https://gitlab.com/doug.salt/cbr for the repository). @