Skip to main content

scriptNOP

A framework for Notification Oriented Paradigm (NOP[1]) implemented in TypeScript.

In the notification Oriented Paradigm (NOP), there are the “factual and causal smart-entities named as Fact Base Elements (FBEs) and Rules that are related to another collaborative notifier smart-entities. Each FBE is related to Attributes and Methods, whereas each Rule to Premises-Conditions and Actions-Instigations. All these entities collaboratively carry out the inference process using notifications, providing solutions to deficiencies of current paradigms” [1].

This implementation provides state-of-the-art features of NOP, in TypeScript, exploring the current limits of object orientation and imperative programming, parallel programming and concurrent programming. The implementation has no dependencies on other libraries and can be used in any TypeScript/JavaScript runtime or browsers. Also, this implementation is REACTIVE IN DEPTH and optionally accepts FUZZY[2] parameters and CUSTOM PROCEDURES like sum of a weighted input of a NEURON[3], and you can still combine it all at the same time.

Contents

Sample application

This program contains an example of an application named “Target shooting”. There is the main thread (state manager), where all the Fact Base Elements are, and there are the secondary threads where the Rules are.

main.ts (main thread):

import {
  App,
  delay,
  FactBaseElement,
} from "https://deno.land/x/script_nop/mod.ts";
App.init({
  numThreads: 1,
  extensionsURLs: [ //for URL to local file: new URL("./my_file.js", import.meta.url).href
    "https://deno.land/x/script_nop/src/extensions/deepEqual.ts",
  ],
  rulesURL: "https://deno.land/x/script_nop/example/rules.ts",
  onNotification: (n: any) => console.log(n), //Using history as a debugger
});
/*
  * Example: Target shooting aplication
  */
class Shooter extends FactBaseElement {
  constructor(fbeName: string) {
    super(fbeName);
  }
  shoot() {
    super.notify(
      "", //path
      {
        gun: {
          bullets: 5,
          pull_trigger: false,
        },
        target: false,
      },
    );
  }
}
const shooter1 = new Shooter("shooter1_name");
shooter1.shoot();

rules.ts (secondary threads):

import { Rule } from "https://deno.land/x/script_nop/mod.ts";
const rule1 = new Rule(
  {
    name: "rule1_name",
    condition: {
      left: {
        attribute: {
          fbe: "shooter1_name",
          attr: "gun.bullets",
        },
      },
      is: ">",
      right: {
        constant: 0,
      },
    },
    action: async (fbes: any) => {
      console.log("loaded gun!!!");
      const bullets = await fbes("shooter1_name").get("gun.bullets"); // get FBE Attribute value (using notifications between Attributes and Actions).
      fbes("shooter1_name").notify( // notify FBE, changes will be automatically sent to the state manager thread.
        "", //path
        {
          target: true,
        },
        "IGNORE_MISSING", //with "IGNORE_MISSING" mode, Attributes missing in "input" (in depth) will not be considered as excluded.
      );
      //Action to Rule notifications are control mechanisms for the NxN relationship between Rules.
      return { gun_loaded: true }; //send notification to the state manager thread, if you have Rules that depend on this Rule activated, they will also receive this notification.
    },
  },
);

For explicit notification modes, if mode includes “IGNORE_MISSING”, Attributes missing in “input” (in depth) will not be considered as excluded, if mode includes “FORCE” Notifications will be forced to Attributes even if the values of these Attributes are not modified, given the Attributes that are contained in the input, and it is possible to combine the two modes (“IGNORE_MISSING_AND_FORCE”). FBEs do not necessarily need to be instantiated. If an FBE does not exist when being referenced by another entity of the NOP core, its instance is created automatically

Defining Conditions

Conditions are implemented in a tree structure, easy for humans to understand. Note that the “.” is reserved in this implementation for path notation, and this implementation handles circular references. Conditions, Attributes values, and Notifications values ​​must be serializable to JSON. Actions and Conditions don’t necessarily need to notify boolean values. However, these entities must notify a JavaScript ‘true’ value for a Rule to activate it. Values ​​like false, ”, null, 0 and undefined will be ‘false’. See examples of Conditions:

//------------------------- TYPES OF CONDITIONS ----------------------
//----------------WITH ONE ATTRIBUTE:
const c: Condition = {
  attribute: {
    fbe: "shooter1_name", //Fact Base Element name.
    attr: "target.person.age", //Attribute name - path notation
  },
};
//----------------WITH ONE CONSTANT:
const c: Condition = {
  constant: {
    name: "joe",
    age: 25,
  },
};
//---------------- CONDITION AS A PREMISE:
/*{
  left: Condition;
  is: string; //"==", ">", "<", etc.
  right: Condition;
}
*/
const c: Condition = {
  left: {
    attribute: {
      fbe: "shooter1_name",
      attr: "target.person.age",
    },
  },
  is: "==",
  right: {
    attribute: {
      fbe: "shooter1_name",
      attr: "target.person.age",
    },
  },
};
//----------------WITH OR, AND, XOR
const c: Condition = {
  and: [ //keys: "or", "and", "xor"
    //ARRAY of sub Conditions.
  ],
};
//----------------WITH CUSTOM PROCEDURE
const c: Condition = {
  op: "+", // "procedure name (registered extension) or operator (+, *, etc)",
  sub_conditions: [ //this vector is the input parameter of the procedure
    //ARRAY of sub Conditions.
  ],
};
//----------------WITH negation
const c: Condition = {
  not: c2, //c2 is one object of type Condition.
};
//----------------WITH OPTIONAL parameters:
//----------------FOR FUZZY LOGIC:
const c: Condition = {
  // ... (Condition body) ...
  min_threshold: { //These parameters can be any type of Condition
    constant: 0.2,
  },
  max_threshold: {
    constant: 0.8,
  },
};
const c: Condition = {
  // ... (Condition body) ...
  exactly: {
    constant: 0.5,
  },
};

Condition with extensions

There is also an extension interface for named procedures, which are used as customized operators in Conditions. These extensions are defined at the beginning of the main thread by the “extensionsURLs” parameter, but they can also be defined manually in the Rules file:

DataMapChild.registerExtensions([customFunc2]);

How to use extensions:

/*
In Conditions:
custonFunc2 = procedure with name "custonFunc2", ex: export default function custonFunc2(items: any[]): any { ...
"items" is an array of result of Conditions ("sub_conditions" parameter).
*/
const c: Condition = {
  op: "customFunc2", //PROCEDURE NAME HERE, the "op" can also be operators like "+", "*", etc. Operators like "and" can be represented by their name in the language ("&&").
  sub_conditions: [ //"sub_conditions" only exists when the "op" attribute in a Condition is filled
    {
      left: {
        attribute: {
          fbe: "shooter1_name",
          attr: "gun.bullets",
        },
      },
      is: "==",
      right: {
        constant: 5,
      },
    },
  ],
};

Combination of Conditions

A combination of different types of Conditions together is possible. Example with simple logic, fuzzy logic and custom procedures:

const c: Condition = {
  or: [
    { // 'or' condition 1
      not: {
        op: "ReLU", //custom procedure name in Condition, input is sub_conditions Array
        sub_conditions: [
          {
            op: "sumOfWeights",
            sub_conditions: [
              {
                attribute: {
                  fbe: "layer1_name",
                  attr: "neurons.0", //paths with .N is valid for vectors
                },
              },
            ],
          },
        ],
      },
    },
    { // 'or' condition 2
      attribute: {
        fbe: "shooter1_name",
        attr: "gun.distance",
      },
      min_threshold: {
        constant: 0.2,
      },
    },
    { // 'or' condition 3
      left: {
        attribute: {
          fbe: "shooter1_name",
          attr: "gun.pull_trigger",
        },
      },
      is: "==",
      right: {
        constant: true,
      },
    },
  ],
};

In the application library, there is an extension procedure named “deepEqual”, which checks in depth if two objects are the same, i.e. compares their parameters, subparameters and etc. It is possible for example an extension procedure that represents a sum of weighted weights of a neuron, it can also be combined with fuzzy logic for the activation threshold of the same.

Rules

See also options for instantiating a Rule:

export type RuleOptions = {
  name: string;
  condition: Condition;
  action: (
    fbes: FBEsFunc,
  ) => Promise<any> | any;
  delay?: number;
  depends?: string[];
};

Instructions to run this project

Basically you just need to clone the project and install the Deno runtime.

# clone project
git clone https://github.com/hviana/scriptNOP.git
# enter the project directory
cd scriptNOP
# install Deno (Mac, Linux)
curl -fsSL https://deno.land/install.sh | sh
# install Deno (Windows/PowerShell)
iwr https://deno.land/install.ps1 -useb | iex
# run project example:
deno run --unstable --allow-all example/main.ts
# run project example (web):
deno run --allow-all --unstable https://raw.githubusercontent.com/hviana/scriptNOP/master/example/main.ts
# bundle scriptNOP lib to any runtime or web browsers:
deno bundle mod.ts nop.js
# bundle scriptNOP lib to any runtime or web browsers (web):
deno bundle https://raw.githubusercontent.com/hviana/scriptNOP/master/mod.ts nop.js

Particularities of this implementation

The framework has several facilities for the developer. Conditions can be composed of SubConditions and can have parameters coming from Attributes notifications, such as fuzzy parameters. Actions have a paradigm-independent nature and are represented directly by a reference to a procedure. This Action procedure can directly notify values to FBEs in order to make changes to the fact base, and notify and receive notifications from Attributes in order to read the fact base. Finally, a Rule can have other N Rules as dependencies or be a dependency for other N Rules (NxN cardinality).

To support all these features, the core of the original NOP was modified. Basically, the Instigations, Methods and Conditions entities were removed, and several new notification paths were added. The Instigations and Methods were removed to make way for a paradigm-independent Action procedure. The Premises were removed because their concept was unified with the Conditions, and it is possible to parameterize a Condition to act as a Premise, being that this concept unification together with the composition of Conditions allow a more generic and powerful interface. For the composition between Conditions, notifications from Conditions to Conditions were created. For the unified concept of Premises and Conditions, notifications of Attributes for Conditions were created.

Actions, since they are a direct reference to a paradigm-independent procedure, need a drastic amount of modifications. When using imperative programming mechanisms, for example, it is unpredictable to know what the code execution flow will be since there may be, for example, code suspension mechanisms such as an “IF”, making it impossible to predict which notifications will be sent and received by this Action procedure. To solve this problem, this Action procedure has access to routines that explicitly notify and receive notifications. When needing an Attribute, there is an asynchronous routine named “get” that sends a notification to the respective Attribute that symbolizes a request to its current value, and the Attribute sends to the Action procedure a notification with its current value that is captured by the routine “get”. In the end, the “get” routine returns the current value of the Attribute inside the Action procedure. To notify FBEs, there is also a routine named “notify”, which transmits to an FBE a notification containing a set of new values ​​for that FBE.

To implement the NxN cardinality dependency between Rules, Actions for Rules notifications are also implemented. When an Action of a Rule “R1” finishes its execution, it notifies all Rules that have “R1” as a dependency.

FBEs notify attributes in depth. That is, if an Attribute “A2” is inside the Attribute “A1”, notifications about “A2” will trigger notifications about “A1”. It is possible to visualize the behavior:

//initial values.
fbe.notify(
  {
    a: {
      b: {
        c: "foo",
      },
      d: true,
    },
  },
);
/*
Conditions/Premises that use "a", "a.b" or "a.b.c" will be notified.
Conditions/Premises that use only "a.d" are not notified, since
the value of "a.d" has not been modified.
*/
fbe.notify(
  {
    a: {
      b: {
        c: "bar",
      },
      d: true,
    },
  },
);
await fbe.get("a.b"); //returns "a.b" Attribute.

The framework also has a debugger procedure that intercepts all notifications between NOP core entities. In this way, it is possible, for example, to save this information in a history or print it on the screen.

An application with this framework can result in a “freeze” of the program if infinite changes of Fact Base Elements states start, given the respective Actions. To minimize this problem and at the same time implement the priority idea of Actions and Rules, when creating a Rule it is possible to insert an optional delay for its Action. Note that there is no need for a “Dispatcher” to queue notifications, as such notifications are implemented using async procedures with delay.

The code is very dense, although every detail has been thought of in order to favor readability and avoid replication. With TypeScript, we have a new way of defining types and programming in an object-oriented style compared to classic object-oriented languages ​​such as Java and C++, which drastically reduces the amount of code. See the following code snippet:

export interface Attribute {
  fbe: string;
  attr: string;
}
// ...
export interface ConditionWithAttribute {
  attribute: Attribute;
}
export interface ConditionAsPremise {
  left: Condition;
  is: string;
  right: Condition;
}
// ...
export interface ConditionWithXor {
  xor: [Condition, Condition, ...Condition[]]; //min 2 Conditions
}
export type Condition =
  & (
    | ConditionWithAttribute
    | ConditionAsPremise
    | ConditionWithConstant
    | ConditionWithNot
    | ConditionWithAnd
    | ConditionWithOr
    | ConditionWithXor
    | ConditionWithFunc
  )
  & FuzzyParameters;
//...
export class Rule {
  static coreLoaded: boolean = false;
  static initialized: boolean = false;
  #conditionTranspiler: ConditionTranspiler;

References

[1] J. M. Simão, C. A. Tacla, P. C. Stadzisz and R. F. Banaszewski, “Notification Oriented Paradigm (NOP) and Imperative Paradigm: A Comparative Study,” Journal of Software Engineering and Applications, Vol. 5 No. 6, 2012, pp. 402-416. doi: https://www.doi.org/10.4236/jsea.2012.56047

[2] Melo, Luiz Carlos & Fabro, João & Simão, Jean. (2015). Adaptation of the Notification Oriented Paradigm (NOP) for the Development of Fuzzy Systems. Mathware& Soft Computing. 22. 1134-5632. url: https://www.researchgate.net/publication/279178301_Adaptation_of_the_Notification_Oriented_Paradigm_NOP_for_the_Development_of_Fuzzy_Systems

[3] F. Schütz, J. A. Fabro, C. R. E. Lima, A. F. Ronszcka, P. C. Stadzisz and J. M. Simão, “Training of an Artificial Neural Network with Backpropagation algorithm using Notification Oriented Paradigm,” 2015 Latin America Congress on Computational Intelligence (LA-CCI), 2015, pp. 1-6, doi: https://doi.org/10.1109/LA-CCI.2015.7435978

About

Author: Henrique Emanoel Viana, a Brazilian computer scientist, enthusiast of web technologies, cel: +55 (41) 99999-4664. URL: https://sites.google.com/site/henriqueemanoelviana

Improvements and suggestions are welcome!