-
Notifications
You must be signed in to change notification settings - Fork 85
/
README.txt
783 lines (563 loc) · 26.8 KB
/
README.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
Intellect
=========
:Info: Intellect is a Domain-specific language and Rules Engine for Python.
:Author: Michael Joseph Walsh
1. What is Intellect
--------------------
Intellect is a DSL ("Domain-Specific Language") and Rule Engine for Python
I authored for expressing policies to orchestrate and control a dynamic
network defense cyber-security platform being researched in The
MITRE Corporation's Innovation Program.
The rules engine provides an intellect, a form of artificial intelligence,
a faculty of reasoning and understanding objectively over a working memory.
The memory retains knowledge relevant to the system, and a set of rules
authored in the DSL that describe a necessary behavior to achieve some
goal. Each rule has an optional condition, and a suite of one or more
actions. These actions either further direct the behavior of the system,
and/or further inform the system. The engine starts with some facts,
truths known about past or present circumstances, and uses rules to infer
more facts. These facts fire more rules, that infer more facts and so
on.
For the platform in the Innovation Program, the network defender uses
the DSL to confer policy, how the platform is to respond to network
events mounted over covert network channels, but there are no direct
ties written into the language nor the rule engine to cyber security
and thus the system in its entirety can be more broadly used in
other domains.
2. TODOS
--------
There are number of improvements I would like to work for future releases:
* Add Support for Multiple Rule Conditions.
* The ANTLR Runtime for Python dependency was left behind with the the release of ANTL4, I would like to port the present Runtime for Java to Python and use.
* Support Python 3.
Please help support these efforts.
.. image:: https://github.com/nemonik/Intellect/raw/master/images/gittip.png
:target: https://www.gittip.com/nemonik/
3. Intellect In The News
---------------------
The September 2013 issue, Volume 37 of Elsevier's "Computers and Security" contains a journal article entitled "`Active cyber defense with denial and deception: A cyber-wargame experiment <http://dx.doi.org/10.1016/j.cose.2013.03.015>`_" describing a computer network security use case for Intellect.
4. Installation
---------------
* To install via `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ use ``easy_install -U Intellect``
* To install via `pip <http://www.pip-installer.org/en/latest/installing.html>`_ use ``pip install Intellect``
* To install via `pypm <http://code.activestate.com/pypm/>`_ use ``pypm install intellect``
* Or download the latest source from `Master <http://github.com/nemonik/Intellect/archives/master>`_ or the most recent tagged release `Tagged Releases <https://github.com/nemonik/Intellect/tags>`_, unpack, and run ``python setup.py install``
5. Dependencies
---------------
* ANTLR3 Python Runtime 3.1.3 (Will contrain you to Python 2.x.)
* Python itself, if you don't already have it. I've tested the code on Python 2.7.1 and 2.7.2., but will likely work on any and all Python 2.x versions. Python 3 at prestent is not supported, because ANTLR3 appears not to support Python 3.
6. Source Code Contributions
----------------------------
The source code is available under the BSD 4-clause license. If you have ideas,
code, bug reports, or fixes you would like to contribute please do so.
Bugs and feature requests can be filed at `Github <http://github.com/nemonik/Intellect>`_.
7. Background
-------------
Many production rule system implementations have been open-sourced, such as
JBoss Drools, Rools, Jess, Lisa, et cetera. If you're familiar with the
Drools syntax, Intellect's syntax should look familiar. (I'm not saying it
is based on it, because it is not entirely, but I found as I was working
the syntax I would check with Drools and if made sense to push in the
direction of Drools, this is what I did.) The aforementioned implementations
are available for other languages for expressing production rules, but it is
my belief that Python is under-represented, and as such it was my thought the
language and rule engine could benefit from being open sourced, and so I put
a request in.
The MITRE Corporation granted release August 4, 2011.
Thus, releasing the domain-specific language (DSL) and Rule Engine to Open
Source in the hopes doing so will extend its use and increase its chances
for possible adoption, while at the same time mature the project with more
interested eyeballs being placed on it.
Starting out, it was initially assumed the aforementioned platform would
be integrated with the best Open Source rules engine available for
Python as there are countless implementation for Ruby, Java, and Perl,
but surprisingly I found none fitting the project's needs. This led to
the thought of inventing one; simply typing the keywords "python rules
engine" into Google though will return to you the advice "to not invent
yet another rules language", but instead you are advised to "just write
your rules in Python, import them, and execute them." The basis for this
advice can be coalesced down to doing so otherwise does not fit with the
"Python Philosophy." At the time, I did not believe this to be true, nor
fully contextualized, and yet admittedly, I had not yet authored a line
of Python code (Yes, you're looking at my first Python program. So,
please give me a break.) nor used ANTLR3 prior to this effort. Looking
back, I firmly believe the act of inventing a rules engine and abstracting it
behind a nomenclature that describes and illuminates a specific domain is
the best way for in case of aforementioned platform the network defender
to think about the problem. Like I said though the DSL and rules engine
could be used for anything needing a "production rule system".
As there were no rules engines available for Python fitting the platforms
needs, a policy language and naive forward chaining rules engine were built
from scratch. The policy language's grammar is based on a subset of Python
language syntax. The policy DSL is parsed and lexed with the help of the
ANTLR3 Parse Generator and Runtime for Python.
8. Facts (Data being reasoned over)
-----------------------------------
The interpreter, the rules engine, and the remainder of the code such as
objects for conferring discrete network conditions, referred to as "facts",
are also authored in Python. Python's approach to the object-oriented programming
paradigm, where objects consist of data fields and methods, did not easily
lend itself to describing "facts". Because the data fields of a Python object
referred to syntactically as "attributes" can and often are set on an
instance of a class, they will not exist prior to a class's instantiation.
In order for a rules engine to work, it must be able to fully introspect an
object instance representing a condition. This proves to be very difficult
unless the property decorator with its two attributes, "getter" and "setter",
introduced in Python 2.6, are adopted and formally used for authoring these objects.
Coincidentally, the use of the "Getter/Setter Pattern" used frequently in
Java is singularly frowned upon in the Python developer community with the
cheer of "Python is not Java".
So, you will need to author your facts as Python object's who attributes
are formally denoted as properties like so for the attributes you would like to
reason over::
class ClassA(object):
'''
An example fact
'''
def __init__(self, property0 = None, property1 = None):
'''
ClassA initializer
'''
self._property0 = property0
@property
def property0(self):
return self._property0
@property0.setter
def property0(self, value):
self._property0 = value
9. The Policy DSL
-----------------
Example with policy files can be found at the path `intellect/examples <https://github.com/nemonik/Intellect/tree/master/intellect/examples>`_.
Policy files must follow the Policy grammar as define in `intellect/grammar/Policy.g <https://raw.github.com/nemonik/Intellect/master/intellect/grammar/Policy.g>`_.
The rest of this section documents the grammar of policy domain-specific language.
9.1 Import Statements (``ImportStmts``)
---------------------------------------
Import statements basically follow Python's with a few limitations. For
example, The wild card form of import is not supported for the reasons
elaborated `here <http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#importing>`_
and follow the Python 2.7.2 grammar. ``ImportStmt`` statements exist only at the same
level of ``ruleStmt`` statements as per the grammar, and are typically at the top of a
policy file, but are not limited to. In fact, if you break up your policy across several
files the last imported as class or module wins as the one being named.
.. _9.2:
9.2 Attribute Statements (``attribute``)
----------------------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/attributeStmt.jpg
The syntax diagram for a ``attributeStmt``.
``attributeStmt`` statements are expressions used to create policy attributes, a form of
globals, that are accessible from rules.
For example, a policy could be written::
import logging
first_sum = 0
second_sum = 0
rule "set both first_sum and second_sum to 1":
agenda-group "test_d"
then:
attribute (first_sum, second_sum) = (1,1)
log("first_sum is {0}".format(first_sum), "example", logging.DEBUG)
log("second_sum is {0}".format(second_sum), "example", logging.DEBUG)
rule "add 2":
agenda-group "test_d"
then:
attribute first_sum += 2
attribute second_sum += 2
log("first_sum is {0}".format(first_sum), "example", logging.DEBUG)
log("second_sum is {0}".format(second_sum), "example", logging.DEBUG)
rule "add 3":
agenda-group "test_d"
then:
attribute first_sum += 3
attribute second_sum += 3
log("first_sum is {0}".format(first_sum), "example", logging.DEBUG)
log("second_sum is {0}".format(second_sum), "example", logging.DEBUG)
rule "add 4":
agenda-group "test_d"
then:
attribute first_sum += 4
attribute second_sum += 4
log("first_sum is {0}".format(first_sum), "example", logging.DEBUG)
log("second_sum is {0}".format(second_sum), "example", logging.DEBUG)
halt
rule "should never get here":
agenda-group "test_d"
then:
log("Then how did I get here?", "example", logging.DEBUG)
containing the two ``atributeStmt`` statements::
first_sum = 0
second_sum = 0
The following rules will increment these two attributes using ``attributeAction``
statements.
Code to exercise this policy would look like so::
class MyIntellect(Intellect):
pass
if __name__ == "__main__":
# set up logging for the example
logger = logging.getLogger('example')
logger.setLevel(logging.DEBUG)
consoleHandler = logging.StreamHandler(stream=sys.stdout)
consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s'))
logger.addHandler(consoleHandler)
myIntellect = MyIntellect()
policy_d = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_d.policy"))
myIntellect.reason(["test_d"])
and the logging output from the execution of the above would be::
2011-10-04 23:56:51,681 example DEBUG __main__.MyIntellect :: first_sum is 1
2011-10-04 23:56:51,682 example DEBUG __main__.MyIntellect :: second_sum is 1
2011-10-04 23:56:51,683 example DEBUG __main__.MyIntellect :: first_sum is 3
2011-10-04 23:56:51,683 example DEBUG __main__.MyIntellect :: second_sum is 3
2011-10-04 23:56:51,685 example DEBUG __main__.MyIntellect :: first_sum is 6
2011-10-04 23:56:51,685 example DEBUG __main__.MyIntellect :: second_sum is 6
2011-10-04 23:56:51,687 example DEBUG __main__.MyIntellect :: first_sum is 10
2011-10-04 23:56:51,687 example DEBUG __main__.MyIntellect :: second_sum is 10
See section 9.3.3.1.2_ ``attributeAction`` for another example.
9.3 Rule Statements (``ruleStmt``)
----------------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/ruleStmt.jpg
The syntax diagram for a ``ruleStmt``.
A rule statement at its simplest looks like so::
rule "print":
then:
print("hello world!!!!")
The rule ``"print"`` will always activate and output ``hello world!!!!`` to the
``sys.stdout``.
A rule will always have an identifier (``id``) in either a ``NAME`` or ``STRING``
token form following Python's naming and ``String`` conventions.
Generally, a rule will have both a ``when`` portion containing the condition
of the rule, as of now a ``ruleCondition``, and an ``action`` described by the
``then`` portion. The ``action`` can be thought of in Python-terms as having more
specifically a suite of one ore more actions.
Depending on the evaluation of ``condition``, facts in knowledge will be matched
and then operated over in the action of the rule.
Such as in the rule ``"delete those that don't match"``, all facts in knowledge
of type ``ClassD`` who's ``property1`` value is either a ``1`` or ``2`` or ``3``
will be deleted in action of the rule.
::
from intellect.testing.ClassCandD import ClassD
rule "delete those that don't match":
when:
not $bar := ClassD(property1 in [1,2,3])
then:
delete $bar
9.3.1 ``agenda-group`` rule property
------------------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/agendaGroup.jpg
The syntax diagram for a ``agendaGroup``.
Optionally, a rule may have an ``agenda-group`` property that allows it to be
grouped in to agenda groups, and fired on an agenda.
See sections 9.2_ ``attribute`` and 9.3.3.1.2_ ``attributeAction`` for examples
of the use of this property.
9.3.2 When
----------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/when.jpg
The syntax diagram for a ``when``.
If present in rule, it defines the condition on which the rule will be activated.
9.3.2.1 Rule Condition (``condition``)
--------------------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/condition.jpg
The syntax diagram for a ``condition``.
A rule may have an optional condition, a boolean evaluation, on the state of objects
in knowledge defined by a Class Constraint (``classConstraint``), and may be
optionally prepended with ``exists`` as follows::
rule rule_c:
when:
exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
then:
print( "matches" + " exist" )
a = 1
b = 2
c = a + b
print(c)
test.helloworld()
# call MyIntellect's bar method as it is decorated as callable
bar()
and thus the action will be called once if there are any object in memory matching
the condition. The action statements ``modify`` and ``delete`` may not be used in
the action if ``exists`` prepends the ``classContraint``.
Currently, the DSL only supports a single ``classConstraint``, but work is ongoing
to support more than one.
9.3.2.1.1 A Class Constraint (``classConstraint``)
--------------------------------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/classConstraint.jpg
The syntax diagram for a ``classConsraint``.
A ``classContraint`` defines how an objects in knowledge will be matched. It defines an
``OBJECTBINDING``, the Python name of the object's class and the optional ``constraint``
by which objects will be matched in knowledge.
The ``OBJECTBINDING`` is a ``NAME`` token following Python's naming convention prepended
with a dollar-sign (``$``).
As in the case of the Rule Condition example::
exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
``$classB`` is the ``OBJECTBINDING`` that binds the matches of facts of type
``ClassB`` in knowledge matching the ``constraint``.
An ``OBJECTBINDING`` can be further used in the action of the rule, but not in the
case where the ``condition`` is prepended with ``exists`` as in the example.
9.3.2.1.2 A Constraint
----------------------
A ``constraint`` follows the same basic ``and``, ``or``, and ``not`` grammar that Python
follows.
As in the case of the Rule Condition example::
exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
All ``ClassB`` type facts are matched in knowledge that have ``property1`` attributes
that ``startwith`` ``apple``, and ``property2`` attributes greater than ``5`` before
evaluated in hand with ``exist`` statement. More on the rest of the constraint follows
in the sections below.
9.3.2.1.2.1 Using Regular Expressions
-------------------------------------
You can also use regular expressions in constraint by simply importing the
regular expression library straight from Python and then using like so as
in the case of the Rule Condition example::
$classB := ClassB( re.search(r"\bapple\b", property1)!=None and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
The regular expression ``r"\bapple\b"`` search is performed on ``property1`` of
objects of type ``ClassB`` in knowledge.
9.3.2.1.2.2 Using Methods
-------------------------
To rewrite a complicated ``constraint``:
````````````````````````````````````````
If you are writing a very complicated ``constraint`` consider moving the
evaluation necessary for the ``constraint`` into a method of fact being
reasoned over to increase readability.
As in the case of the Rule Condition example, it could be rewritten to::
$classB := ClassB(property1ContainsTheStrApple() and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
If you were to add the method to ClassB::
def property1ContainsTheStrApple()
return re.search(r"\bapple\b", property1) != None
Of a class and/or instance:
```````````````````````````
This example, also demonstrates how the ``test`` module function ``greaterThanTen``
can be messaged the instance's ``property2`` attribute and the function's return
evaluated, and a call to the instance's ``aMethod`` method can be evaluated for
a return of ``"a"``.
9.3.3 Then
----------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/then.jpg
The syntax diagram for a ``then``.
Is used to define the suite of one-or-more ``action`` statements to be called
firing the rule, when the rule is said to be activated.
9.3.3.1 Rule Action (Suite of Actions)
--------------------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/action.jpg
The syntax diagram for an ``action``.
Rules may have a suite of one or more actions used in process of doing something,
typically to achieve an aim.
9.3.3.1.1 Simple Statements (``simpleStmt``)
--------------------------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/simpleStmt.jpg
The syntax diagram for a ``simpleStmt``.
``simpleStmts`` are supported actions of a rule, and so one can do the following::
rule rule_c:
when:
exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
then:
print("matches" + " exist")
a = 1
b = 2
c = a + b
print(c)
test.helloworld()
bar()
The ``simpleStmt`` in the action will be executed if any facts in knowledge
exist matching the condition.
To keep the policy files from turning into just another Python script you
will want to keep as little code out of the suite of actions and thus the policy
file was possible... You will want to focus on using ``modify``, ``delete``,
``insert``, ``halt`` before heavily using large amounts of simple statements. This
is why ``action`` supports a limited Python grammar. ``if``, ``for``, ``while`` etc
are not supported, only Python's ``expressionStmt`` statements are supported.
.. _9.3.3.1.2:
9.3.3.1.2 ``attributeAction``
-----------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/attributeStmt.jpg
The syntax diagram for a ``attributeStmt``.
``attributeAction`` actions are used to create, delete, or modify a policy
attribute.
For example::
i = 0
rule rule_e:
agenda-group "1"
then:
attribute i = i + 1
print i
rule rule_f:
agenda-group "2"
then:
attribute i = i + 1
print i
rule rule_g:
agenda-group "3"
then:
attribute i = i + 1
print i
rule rule_h:
agenda-group "4"
then:
# the 'i' variable is scoped to then portion of the rule
i = 0
print i
rule rule_i:
agenda-group "5"
then:
attribute i += 1
print i
# the 'i' variable is scoped to then portion of the rule
i = 0
rule rule_j:
agenda-group "6"
then:
attribute i += 1
print i
If the rules engine is instructed to reason seeking to activate
rules on agenda in the order describe by the Python list
``["1", "2", "3", "4", "5", "6"]`` like so::
class MyIntellect(Intellect):
pass
if __name__ == "__main__":
myIntellect = MyIntellect()
policy_c = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_c.policy"))
myIntellect.reason(["1", "2", "3", "4", "5", "6"])
The following output will result::
1
2
3
0
4
5
When firing ``rule_e`` the policy attribute ``i`` will be incremented by a value
of ``1``, and print ``1``, same with ``rule_f`` and ``rule_g``, but ``rule_h``
prints 0. The reason for this is the ``i`` variable is scoped to ``then`` portion
of the rule. ``Rule_i`` further illustrates scoping: the policy attribute ``i``
is further incremented by ``1`` and is printed, and then a variable ``i`` scoped to
``then`` portion of the rule initialized to ``0``, but this has no impact on
the policy attribute ``i`` for when ``rule_j`` action is executed firing the rule
the value of ``6`` is printed.
9.3.3.1.3 ``learn`` action
--------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/learnAction.jpg
:scale: 50 %
The syntax diagram for a ``learnAction``.
A rule entitled ``"Time to buy new sheep?"`` might look like the following::
rule "Time to buy new sheep?":
when:
$buyOrder := BuyOrder( )
then:
print( "Buying a new sheep." )
modify $buyOrder:
count = $buyOrder.count - 1
learn BlackSheep()
The rule above illustrates the use of a ``learn`` action to learn/insert
a ``BlackSheep`` fact. The same rule can also be written as the following
using ``insert``::
rule "Time to buy new sheep?":
when:
$buyOrder := BuyOrder( )
then:
print( "Buying a new sheep." )
modify $buyOrder:
count = $buyOrder.count - 1
insert BlackSheep()
9.3.3.1.4 ``forget`` action
---------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/forgetAction.jpg
The syntax diagram for a ``forgetAction``.
A rule entitled ``"Remove empty buy orders"`` might look like the following::
rule "Remove empty buy orders":
when:
$buyOrder := BuyOrder( count == 0 )
then:
forget $buyOrder
The rule above illustrates the use of a ``forget`` action to forget/delete
each match returned by the rule's condition. The same rule can also be written
as the following using ``delete``::
rule "Remove empty buy orders":
when:
$buyOrder := BuyOrder( count == 0 )
then:
delete $buyOrder
Note: cannot be used in conjunction with ``exists``.
9.3.3.1.5 ``modify`` action
---------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/modifyAction.jpg
The syntax diagram for a ``modifyAction``.
The following rule::
rule "Time to buy new sheep?":
when:
$buyOrder := BuyOrder( )
then:
print( "Buying a new sheep." )
modify $buyOrder:
count = $buyOrder.count - 1
learn BlackSheep()
illustrates the use of a ``modify`` action to modify each ``BuyOrder`` match
returned by the rule's condition. Cannot be used in conjunction with ``exists``
rule conditions. The ``modify`` action can also be used to chain rules, what
you do is modify the fact (toggle a boolean property, set a property's value,
etc) and then use this property to evaluate in the proceeding rule.
9.3.3.1.6 ``halt`` action
-------------------------
.. figure:: https://github.com/nemonik/Intellect/raw/master/images/haltAction.jpg
The syntax diagram for a ``haltAction``.
The following rule::
rule "End policy":
then:
log("Finished reasoning over policy.", "example", logging.DEBUG)
halt
illustrates the use of a ``halt`` action to tell the rules engine to halt
reasoning over the policy.
10. Creating and using a Rules Engine with a single policy
---------------------------------------------------------
At its simplest a rules engine can be created and used like so::
import sys, logging
from intellect.Intellect import Intellect
from intellect.Intellect import Callable
# set up logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s', stream=sys.stdout)
intellect = Intellect()
policy_a = intellect.learn(Intellect.local_file_uri("../rulesets/test_a.policy"))
intellect.reason()
intellect.forget_all()
It may be preferable for you to sub-class ``intellect.Intellect.Intellect`` class in
order to add ``@Callable`` decorated methods that will in turn permit these methods
to be called from the action of the rule.
For example, ``MyIntellect`` is created to sub-class ``Intellect``::
import sys, logging
from intellect.Intellect import Intellect
from intellect.Intellect import Callable
class MyIntellect(Intellect):
@Callable
def bar(self):
self.log(logging.DEBUG, ">>>>>>>>>>>>>> called MyIntellect's bar method as it was decorated as callable.")
if __name__ == "__main__":
# set up logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s',
#filename="rules.log")
stream=sys.stdout)
print "*"*80
print """create an instance of MyIntellect extending Intellect, create some facts, and exercise the MyIntellect's ability to learn and forget"""
print "*"*80
myIntellect = MyIntellect()
policy_a = myIntellect.learn(Intellect.local_file_uri("../rulesets/test_a.policy"))
myIntellect.reason()
myIntellect.forget_all()
The policy could then be authored, where the ``MyIntellect`` class's ``bar`` method
is called for matches to the rule condition, like so::
from intellect.testing.subModule.ClassB import ClassB
import intellect.testing.Test as Test
import logging
fruits_of_interest = ["apple", "grape", "mellon", "pear"]
count = 5
rule rule_a:
agenda-group test_a
when:
$classB := ClassB( property1 in fruits_of_interest and property2>count )
then:
# mark the 'ClassB' matches in memory as modified
modify $classB:
property1 = $classB.property1 + " pie"
modified = True
# increment the match's 'property2' value by 1000
property2 = $classB.property2 + 1000
attribute count = $classB.property2
print "count = {0}".format( count )
# call MyIntellect's bar method as it is decorated as callable
bar()
log(logging.DEBUG, "rule_a fired")