\[\DeclareMathOperator\arcsinh{arcsinh} \DeclareMathOperator\arccosh{arcsinh} \DeclareMathOperator\arctanh{arcsinh} \DeclareMathOperator\sgn{sgn}\]

1. Quick Start

Expressions can be used in query filters, for result sorting, and to select result fields. Below are three examples showing each case.

1.1. filter

This filter requires the value of the arc from "A" to its neighbor to be greater than 10 and the neighbor id must equal "B" or start with "C" or end with "d".

graph.Neighborhood( "A",
    filter=".arc.value > 10 && next.id in {'B', 'C*', '*d'}"
)

1.2. rank

This custom ranking function will divide the neighbor’s "score" property by the arc value and multiply the result by the anchor’s "boost" property. The function’s output value is used for sorting the query results.

graph.Neighborhood( "A",
    rank="vertex['boost'] * (next['score'] / .arc.value)",
    sortby=S_RANK
)

1.3. select

This select statement formats the query result to include the neighbor’s "score" property, the value of the arc from "A" to its neighbor, and a computed value with custom name "RATIO".

graph.Neighborhood( "Alice",
    select="score; .arc.value; RATIO: next['score'] / .arc.value"
)

See Select Evaluator for details.

1.4. Quick Reference

Section Description

Define Functions with Define()

Define an expression and assign it to a name

Evaluate Expressions with Evaluate()

Evaluate an expression

Basic Expression Syntax

Expression syntax and operators

Attributes and Properties

Arc Attributes

Arc attributes (relationship type, value, etc.)

Vertex Attributes

Vertex attributes (id, degree, timestamps, etc.)

Vertex Properties

User defined vertex properties (key/value pairs)

Constants

Pre-defined numeric constants for use in expressions

Current Environment

Variables derived from current execution environment

Functions

Mathematical Functions

Basic math functions (log, sqrt, sin, etc.)

Type Cast and Checks

Check and convert data types (int, isstr, len, etc.)

String Functions

Basic string operations (join, replace, etc.)

Property Lookup

Retrieve vertex property by key

Enumeration

Encode or decode enumerations (relenc, typedec, etc.)

Similarity (Feature Vectors)

Compute vector similarity (cosine, hamdist, etc.)

Control Functions

Control functions (do, firstval, returnif, halt, etc.)

Variadic Aggregator Functions

Aggregate multiple variable arguments (sum, prod, mean, etc.)

Simple Ranking Functions

Basic functions for ranking search results (rank, prox, etc.)

Collector Staging Functions

Collect result data in expressions (stage, collectif, etc.)

Evaluator Memory

Operations on Single Memory Locations

These functions perform operations on a specified memory location

Storing and Loading

Write and read memory data location (store, write, load, etc.)

Stack

Memory stack operations (push, pop, etc.)

Moving Data

Move data between memory locations (mov, xchg, etc.)

Increment and Decrement

Inc/dec data in memory location (inc, decif, etc.)

Comparator

Compare data in two memory locations (equ, gt, lte, etc.)

Arithmetic

Arithmetic operations on memory location (add, divif, etc.)

Bitwise

Bitwise operations on memory location (shr, and, xorif, etc.)

Vector Arithmetic

AVX accelerated bytearray vector operations (dp_pi8, cos_pi8, etc.)

Various Functions

Smoothing and counting (smooth, count, etc.)

Indexing

Various memory indexing helper functions (modindex, etc.)

Operations on Multiple Memory Locations

These functions perform array operations on multiple memory locations

Set and Copy

Assign and copy data (mset, mcopy, etc.)

Heap Operations

Min/max-heap operations (mheapifymin, mheappushmin, etc.)

Sorting

Sort a memory range (msort, mreverse, etc.)

Type Cast

Convert data in multiple memory locations (mint, mreal, etc.)

Increment and Decrement

Inc/dec all locations in memory range (minc, mdec, etc.)

Arithmetic

Arithmetic on multiple memory locations (mmul, msqrt, etc.)

Numeric

Numeric operations on multiple locations (mround, mabs, etc.)

Logarithms and Exponential Functions

Apply log/exp to memory range (mlog2, mlog10, mexp, etc.)

Trigonometric and Hyperbolic

Apply trig/hyp functions to memory range (msin, macosh, etc.)

Bitwise Operations

Bit shift and logical operations on memory range (mshr, mxor, etc.)

Hashing

Hash multiple memory locations (mhash)

Aggregation

Aggregate memory range (msum, msumsqr, mharmmean, etc.)

Array Probing

Search memory range (mcount, mcmp, msubset, etc.)

Special Purpose Operations

probealtarray()

probealtarray() compares special array object to probe stored in memory range and computes a match score.

synarc.decay() and synarc.xdecay()

synarc.decay() and synarc.xdecay() compute time-decayed arc values.

2. Overview

VGX has a built-in evaluator engine for executing user-defined expressions. Custom formulas can be used in traversal filters (filter=), result ranking (rank=) and rendering of selected fields (select=). For example:

graph.Neighborhood(
    "Alice",
    filter  = "next.id in {'B*','*ch*'}",
    rank    = "next.arc.value / next['age']",
    select  = ".id; age",
    sortby  = S_RANK
)

The expression syntax supports grouping, nesting, logical operators, comparison operators, property lookup, array indexing, membership tests for discrete and continuous sets, and all the standard mathematical operators and functions. Expressions can be pre-defined and referenced by name in graph queries.

A comprehensive library of functions, constants and environment variables is available for use in expressions.

It is possible to define and pass a memory array object to queries. Expressions evaluated as part of a query may then read or write elements of the memory array, opening up many possibilities for algorithm design. For instance, variables captured by a filter expression may be passed as input to a ranking expression. The same memory array object can be used by different queries enabling information sharing from one query to the next. Other uses include value aggregation, creating histograms, tracking graph cycles, and computing averages or deviations just to name a few.

Expressions can also be evaluated stand-alone using pyvgx.Graph.Evaluate(). For example:

graph.Evaluate( "1+1" ) # -> 2
graph.Evaluate( "log( (2.1e3 + 4.5) * (pi - sin(6.7)) )" ) # -> 8.66...

2.1. Safe Evaluation

Due to the schemaless and dynamic nature of data values encountered during traversal the evaluator uses a best-effort approach when computing results. In general the evaluator will compute expressions "the way you intended." Where it deviates from many programming languages is in the lenient handling of operations on incompatible objects, zero division, domain errors, accessing non-existent properties, and out of range array indices.

It is impossible to write an expression that will crash, lock or leave the system in a corrupted state.

2.1.1. Incompatible Objects

Operations on incompatible objects will generally produce results as if the operations were not performed, which usually means keeping the leftmost operand. For example, "5 / 'a string'" will produce 5 and "5 / 'a string' + 1" will produce 6.

2.1.2. Divide by Zero

The result of zero division is generally a "very large number". For example, 1/0 evaluates to 8.5e37. This interpretation of zero division ensures formulas are computable even as divisors approach (and reach) zero. The divisor -0.0 is an exception in that 1/-0.0 produces -inf and 0/-0.0 produces nan.

2.1.3. Domain Errors

Some mathematical functions accept arguments outside their defined domains. Roots of negative numbers produce zero (imaginary numbers are not supported.) Logarithms of zero or negative numbers produce a negative number which depends on the base. For example sqrt(-1) evaluates to 0, log(0) evaluates to -745.0 and log2(-1) evaluates to -1074.0. Computation involving roots and logarithms will always produce numeric results.

Other functions, such as the trigonometric functions, will produce nan for invalid arguments. For instance, asin(2) results in nan as does sqrt(-1) + asin(2) + 5.

2.1.4. Non-existent Vertex Properties

Lookup of a non-existent property will be processed as if the lookup did not take place. For example, if property "x" does not exist then 1 + vertex['x'] + 2 evaluates to 3 and 5 / vertex['x'] evaluates to 5, even though isnan( vertex['x'] ) yields true.

Following this logic, cos( vertex['x'] ) is nan while both 1 + cos( vertex['x'] ) and cos( 0 + vertex['x'] ) return 1.

Default values for non-existent properties can be obtained in two ways. The first method is function vertex.property( "x", default ) which returns property "x" of current tail (i.e. vertex['x']) if it exists, or default if the property does not exist. The second method is function firstval(), which is variadic (i.e. any number of arguments) and returns the leftmost argument that is not nan. For example, firstval( vertex['x'], 2.5 ) returns 2.5 if property "x" does not exist and firstval( next['x'], asin(next['y']), pi/2 ) returns Π/2 if the two preceding arguments both produce nan.

Substitute next or prev for vertex to access properties of the current head or previous tail when traversing arcs. E.g. next.property( x ) or prev.property( x ).

2.1.5. Out of Range Subscripts

Operations involving array subscripts employ modulo indexing which guarantees safety. For example, if Evaluator Memory is used and the memory array object has 128 slots store(5, 100), store(133, 100), and store(-123, 100) are equivalent; they all assign value 100 to memory location 5. The size of memory array objects is always a power of two. The last four slots are aliased with registers R1, R2, R3 and R4. There is also a stack which grows downward starting at the slot just before the registers. It is the responsibility of the programmer to ensure algorithm correctness and avoid undesired modulo indexing or aliasing conflicts between array subscripts, registers, and stack.

2.2. Data Types

The evaluator engine can operate on objects of various types. If a function is called with an incompatible argument the result is generally the unprocessed argument, such as sin('hello') which simply returns 'hello'.

Many operators and functions automatically adjust their behavior according to the argument types. For example, the function hash() produces a 64-bit integer hash of the argument, which can be of any type, but uses different algorithms depending on the type. (hash( 123 ), hash( 'hello' ) and hash( vector ) are all valid.)

The final result of an expression is interpreted according to the context where it appears. Filters are concerned with matching (positivity), ranking is concerned with numeric scores and select statements expect lists of objects to render.

Table 1. Evaluator Data Types
Type Positive Match As Rank Score Type Check Description

integer

> 0

real( self )

isint()

64-bit signed integer.

real

> 0.0

self

isreal()

64-bit (double precision) floating point.

string

true

real( first_5_bytes_as_int )

isstr()

Text string or raw bytes. May appear as function argument or return value.

group

first item

first item value

Comma separated list of objects

range

(N/A)

(N/A)

Continuous set of numbers from a to b range[a, b> for use with the in and notin operators.

set

false

0.0

Discrete set of objects {a, b, c, …​} for use with the in and notin operators.

vector

true

real( address )

isvector()

Object of type <vector>

bitvector

!= 0

real( self )

isbitvector()

64-bit unsigned integer for use with certain operations that handle bit masks and integers differently.

keyval

value > 0.0

value

2-tuple (key, value) where key is a 32-bit signed integer and values is a single precision float.

vertex

true

real( address )

Object of type pyvgx.Vertex

vertexid

true

real( address )

The identifier string of an object of type pyvgx.Vertex

nan

false

0.0

isnan()

Not a number

null

false

0.0

No value

inf

true

0.0

Infinity

2.2.1. Type integer

graph.Evaluate( "1000" )   # -> 1000
graph.Evaluate( "-1000" )  # -> -1000
graph.Evaluate( "01000" )  # -> 1000   leading zeros ignored
graph.Evaluate( "0x1000" ) # -> 4096   0x prefix for hexadecimal
graph.Evaluate( "0X1000" ) # -> 4096   0X also accepted
graph.Evaluate( "0xffffffffffffffff" ) # -> -1   2's complement
graph.Evaluate( "0xfffffffffffffffe" ) # -> -2   2's complement

2.2.2. Type real

graph.Evaluate( "3.14" )  # -> 3.14
graph.Evaluate( ".123" )  # -> 0.123
graph.Evaluate( "1e3" )   # -> 1000.0
graph.Evaluate( "-.1e1" ) # -> 1.0

2.2.3. Type string

graph.Evaluate( "'hello'" ) # -> 'hello'

2.2.4. Type group

graph.Evaluate( "(1,2,3,4)" ) # -> 1
graph.Evaluate( "('hello',2,3,4)" ) # -> 'hello'

2.2.5. Type range

graph.Evaluate( "0.999 in range(1,3)" ) # -> 0
graph.Evaluate( "1 in range(1,3)" ) # -> 1
graph.Evaluate( "2.999 in range(1,3)" ) # -> 1
graph.Evaluate( "3 in range(1,3)" ) # -> 0

2.2.6. Type set

graph.Evaluate( "{1, 2, 3}" ) # -> '<set>'
graph.Evaluate( "2 in {1, 2, 3}" ) # -> 1
graph.Evaluate( "2.999 in {1, 2, 3}" ) # -> 0

2.2.7. Type vector

X = graph.NewVertex( "X" )
Y = graph.NewVertex( "Y" )
X.SetVector( [('a',1),('b',0.5),('c',0.25)] )
Y.SetVector( [('a',1),('b',0.75),('c',0.5)] )

E = graph.Evaluate

### Vector type
E( "next.vector", head="X" ) # -> '<vector>'

### Length
E( "len( next.vector )", head="X" ) # -> 3

### Magnitude
E( "asreal( next.vector )", head="X" ) # -> 1.14564394951

### Fingerprint
E( "hash( next.vector )", head="X" ) # -> -3579455383477239039

### Address
E( "int( next.vector )", head="X" ) # -> 2337927647264

### Element of
E( "'c' in next.vector", head="X" ) # -> 1
E( "'d' in next.vector", head="X" ) # -> 0

### Similarity
g.Evaluate( "sim( prev.vector, next.vector )", tail="X", head="Y" ) # -> 0.97265625

2.2.8. Type bitvector

graph.Evaluate( "0b11110000" ) # -> '<bitvector 0x00000000000000f0>'

2.2.9. Type keyval

graph.Evaluate( "keyval(1000,0.5)" ) # -> '<keyval (1000,0.5)>'

### Key
graph.Evaluate( "int( keyval(1000,0.5) )" ) # -> 1000

### Value
graph.Evaluate( "real( keyval(1000,0.5) )" ) # -> 0.5

2.2.10. Type vertex

V = graph.NewVertex( "Node1" )
V['x'] = 1000
graph.Connect( "Node1", "to", "X" )
graph.Connect( "Node1", "to", "Y" )

### Attribute
graph.Evaluate( "next.odeg", head=V ) # -> 2
graph.Evaluate( "next.tmc", head=V ) # -> 1642822680

### Internalid string
graph.Evaluate( "str(next)", head=V ) # -> '76fd13f0d0135ed4c7ed813e1c3db986'

### Address
graph.Evaluate( "int( next )", head=V ) # -> 2336660804768

### Property
graph.Evaluate( "int( next )", head=V ) # -> 2336660804768
graph.Evaluate( "'x' in next", head=V ) # -> 1
graph.Evaluate( "'y' in next", head=V ) # -> 0
graph.Evaluate( "next['x']", head=V ) # -> 1000

2.2.11. Type vertexid

V = graph.NewVertex( "Node1" )
graph.Evaluate( "next.id", head=V ) # -> 'Node1'
hex(graph.Evaluate( "hash( next.id )", head=V )) # -> '-0x38127ec1e3c2467a'

2.2.12. Type nan

graph.Evaluate( "nan" ) # -> nan
graph.Evaluate( "acos(2)" ) # -> nan  # domain error
graph.Evaluate( "acos(2) == nan" ) # -> 1
graph.Evaluate( "1.8e308" ) # -> nan  # out of range

2.2.13. Type null

graph.Evaluate( "null" )
graph.Evaluate( "isnan( null )" ) # -> 1

2.2.14. Type inf

graph.Evaluate( "inf" ) # -> inf
graph.Evaluate( "1e300 * 1e300" ) # -> inf
graph.Evaluate( "1e300 * 1e300 == inf" ) # -> 1

### Infinity is considered numeric
graph.Evaluate( "isnan( inf )" ) # -> 0

### NOTE: The expression language is designed to produce
###       numeric results even when infinity is involved
###       in the computation. Dividing by zero produces
###       "a very large number" and dividing by infinity
###       produces zero.
graph.Evaluate( "1/inf" ) # -> 0.0
graph.Evaluate( "1**inf" ) # -> 1.0
graph.Evaluate( "1.000001**inf" ) # -> 1.7976931348623157e+308
graph.Evaluate( "0.999999**inf" ) # -> 0.0
graph.Evaluate( "inf/inf" ) # -> nan
graph.Evaluate( "inf-inf" ) # -> nan
graph.Evaluate( "-1/-0.0" ) # -> inf # Special case
graph.Evaluate( "-1/-0.0 == inf" ) # -> 1

2.3. No Short Circuiting

There are no short-circuit operators, leaving no doubt about which parts of expressions are evaluated: all sub-expressions within a complex expression are always executed.

For example "1 > 2 && inc( R1 ) > 10" will naturally always be false (due to 1 > 2 part), but will regardless execute inc( R1 ).

This behavior is different from many programming languages, so beware.

Conditional execution requires explicit use of a conditional function. The above example may be rewritten to perform operations conditionally (also replacing 1 > 2 with the more useful .arc.value > 2), like this:

"incif( .arc.value > 2, R1 ) && load( R1 ) > 10". Here the value in register R1 is only incremented when .arc.value > 2 and the entire expression evaluates to true after 11 iterations where the .arc.value condition was met.

Be careful when writing filters to avoid unintended overhead in computation and memory access.

There are exactly two exceptions to the no short-circuiting execution policy. The functions return() and returnif() can be placed within an expression to terminate execution at that point in the expression.

2.4. Expression Examples

Suppose we have created a graph with relationships between friends. The following examples show how we can filter, rank results and select fields to return.

2.4.1. Filter Example

Find Alice’s friends and pets
graph.Neighborhood(
    "Alice",
    filter  = "( .arc.type in {'friend','knows','likes'} && .type == 'person' ) || ( .arc.type == 'owns' && .type == 'pet' )"
)

Here we use filter= to match three different relationships ("friend", "knows", or "likes") using the in { …​ } set operator and combine sub-expressions with boolean operators && and ||.

Complex matching logic like this is not possible using arc= and neighbor= parameters alone. However, in many cases it may be more efficient to also include arc= and neighbor= in the query, since they may help reduce the amount of processing performed by the expression evaluator by early termination of non-matching filters.

2.4.2. Dynamic Ranking Example

Boost Alice’s friends
graph.Neighborhood(
    "Alice",
    sortby = S_RANK,
    rank   = ".arc.value * ( 1 + 9 * (.arc.type == 'friend')) / next.deg"
)

Here we use rank= to specify a custom scoring function. To use the computed scores for sorting results we must also specify sortby=S_RANK. In this example results are ordered according to arc values divided by neighbor’s degree, boosting relationships of type 'friend' by a factor 10.

2.4.3. Select Result Fields Example

Include selected and computed properties in result
graph.Neighborhood(
    "Alice",
    select = "age; address; SCORE1: .arc.value / (1 + log( next.odeg / next.ideg )); LABEL1: hash( next.id )"
)

The select statement specifies a semicolon-separated list of items to return. Items can be attributes, vertex properties, or computed values identified by labels. If the item is a single token it is interpreted as a vertex property key, unless it starts with a period "." which is interpreted as an attribute (such as ".id" which is short for "next.id").

When the item is a labeled expression "label : …​" any valid expression can be specified to dynamically compute values. The computed values will be identifiable in the result by their labels.

This example includes Alice’s neighbors' age, address, some computed number identified by label SCORE1, and another computed value identified by LABEL1.

3. Expression Language Reference

Expressions are constructed using syntax similar to many programming languages, and offer a wide variety of built-in function. The following sections summarize all available symbols, functions and constructs.

Expressions are available to use in query parameters filter=, rank= and select=.

3.1. Define Functions with Define()

An expression may be defined and stored in the graph for future use.

3.1.1. Syntax

pyvgx.Graph.Define( expression )

Create a new function formula that can be used by queries for filtering and ranking. The expression parameter is a string of the form <name> := <formula>.

3.1.2. Parameters

Parameter Type Description

expression

str

Expression definition of the form <name> := <formula> where <formula> is any valid evaluator expression and <name> is a label which is used later by queries to invoke the formula.

3.1.3. Return Value

This method does not return anything.

3.1.4. Remarks

Pre-defined expressions are stored in the graph object on which the Define() method was invoked. Expressions defined for a graph instance are exclusive to that instance and cannot be referenced by other graphs.

It is recommended to pre-define complex functions before using them in queries since parsing and compiling occur only when the expression is defined. Eliminating this overhead may help speed up query execution, and in cases where the same expression is used by multiple queries it is easier to maintain consistent functionality.

Make sure type enumerations exist for all arc relationship types and vertex types that appear in the expression before the expression is defined. Otherwise the expression will not be correctly evaluated.

3.2. Evaluate Expressions with Evaluate()

An expression may be evaluated outside the context of a query for testing or for operations that do not require a graph query.

3.2.1. Syntax

pyvgx.Graph.Evaluate( expression[, tail[, arc[, head[, vector[, memory ]]]]] )

Execute the expression, which is a string defining a new formula, or referencing a pre-defined formula. The optional parameters are used to supply information that may be referenced in the formula.

3.2.2. Parameters

Parameter Type Default Description

expression

str

Expression to be evaluated

tail

str or pyvgx.Vertex

None

Vertex whose attributes and properties may be accessed via the various vertex* references.

arc

Relationship specifier

()

Arc whose attributes may be accessed via the various next.arc* references.

head

str or pyvgx.Vertex

None

Vertex whose attributes and properties may be accessed via the various next* references.

vector

<vector> or Python list

[]

Similarity vector which may be accessed via the vector reference.

memory

pyvgx.Memory or int

4

Memory array object which may be accessed using the various memory operations.

3.2.3. Return Value

This method returns the result of the evaluation, which may be an integer, a floating point object (including nan or inf), a string, or other object.

3.2.4. Remarks

This method is useful for experimenting with expressions before using them with queries. It can also be used for pre/post processing memory arrays outside the context of a query. For example, if a query was used to gather information into a memory array object this method can then be used to access and further process that information.

All processing within the evaluator occurs outside the Python interpreter, allowing for multi-threaded operation and in some cases more efficient single-threaded processing. For instance, running msum() on a large memory array of 100,000,000 floating point numbers is about 5 times faster than Python’s native sum() function on an equivalent Python array. Moreover, running mradd() on the same memory array is about 50 times faster than performing the equivalent operation in a pure Python loop.

3.3. Basic Expression Syntax

Expressions are evaluated according to operator precedence and associativity, as commonly used in most programming languages. The table below summarizes the syntax rules.

Table 2. Symbolic Operators
Precedence Operator Description Associativity

1

( )
{ }

Group (parentheses)
Set

Left-to-right

2

()
[]
.

Function call
Subscript (property)
Attribute

Left-to-right

3

` [[_opunaryplus]] + `-` [[_opunaryminus]] + `!` [[_oplogicalnot]] + `~` [[_opbitwisenot]] + `**+

Unary plus
Unary minus
Logical NOT
Bitwise NOT (one’s complement)
Exponentiation

Right-to-left

4

in range(…​)
notin range(…​)
in { …​ }
notin { …​ }

In numeric range
Not in numeric range
Is element of set
Is not element of set

Left-to-right

5

*
/
%

Multiplication
Division
Modulo (remainder)

Left-to-right

6

+
-

Addition
Subtraction

7

<<
>>

Bitwise left shift
Bitwise right shift

Left-to-right

8

<
<=
>
>=

Less than
Less than or equal to
Greater than
Greater than or equal to

Left-to-right

9

==
!=

Equal to
Not equal to

Left-to-right

10

&

Bitwise AND

Left-to-right

11

^

Bitwise XOR

Left-to-right

12

|

Bitwise OR

Left-to-right

13

&&

Logical AND

Left-to-right

14

||

Logical OR

Left-to-right

15

?:

Ternary conditional

Right-to-left

16

:=
=

Expression definition
Variable assignment

Right-to-left

17

:

Label (select= only)

Right-to-left

18

,
;

Comma
Semicolon

Left-to-right

3.3.1. Group

Operations may be enclosed in parentheses ( ) to override default precedence rules. Groups may be nested to construct any mathematical formula or logical expression. Nesting is unlimited.

1 / ( ( 2 + 3 ) * ( 4 + 5 ) )                   // -> 0.02222...

A group can also be used to perform one or more "hidden" operations needed for their side-effects. When multiple expressions separated by comma , appear within the group only the value of the first expression becomes the value of the group. All subsequent expressions are evaluated but their return values are discarded.

( 1, 2, 3 ) + ( 1.5, 2.5, 3.5 )                 // -> 2.5
10 * ( 2, store( R1, pi ) )                     // -> 20
4 ** 3 - 2 + 1                                  // -> 63
4 ** (3 - 2) + 1                                // -> 5
4 ** 3 - (2 + 1)                                // -> 61
4 ** (3 - 2 + 1)                                // -> 16

3.3.2. Set

A set of discrete numbers or objects is created by placing them within braces { }, items separated by commas ,.

{ 3, 4, 5, 'hello', 6.7 }

3.3.3. Function Call

A pair of parentheses (…​) with optional comma , separated arguments is interpreted as a function call when the preceding token is a string.

random()
log( 1.23 )
mmul( 0, 31, pi )

3.3.4. Subscript

A literal string 's' within a pair of brackets [ 's' ] denotes a property lookup when the preceding token is a vertex object. Properties are readonly.

vertex[ 'prop1' ]
next[ 'value' ]

3.3.5. Attribute

The dot . operator is used to access vertex attributes, arc attributes and various context information. Attributes are readonly.

vertex.id
next.arc.value
graph.size

3.3.6. Unary Plus/Minus

Unary plus + and minus set the sign of a numeric value.

-1                      // -> -1
-(+2 - +5)              // -> 3
-7+-1--8                // -> 0

3.3.7. Logical NOT

The exclamation point ! negates a logical value.

!0                      // -> 1
!1                      // -> 0
!2                      // -> 0
!( true || false )      // -> 0
!( !true || false )     // -> 1

3.3.8. Bitwise NOT

A tilde ~ denotes one’s complement of a numeric value, i.e. all bits are inverted.

~0                      // -> -1
~1                      // -> -2
~0xffffffffffffffff     // -> 0

3.3.9. Exponentiation

A pair of infix asterisks ** raises a number to the power of another number.

16 ** 2                 // -> 256
100.0 ** 0.5            // -> 10.0
e ** -0.1               // -> 0.9048...

3.3.10. In Numeric Range

The in and notin operators followed by range( a, b ) express the numeric range conditions ∈ [ a, b > and ∉ [ a, b >, respectively.

5 in range( 2, 6 )          // -> true
5 in range( 2, 5 )          // -> false
pi in range( 3.1, 3.2 )     // -> true
10 notin range( 1, 5 )      //  -> true

3.3.11. Member of Set

The in and notin operators followed by set { x1, x2, …​, xn } express the discrete set membership conditions ∈ { x1, x2, …​, xn } and ∉ { x1, x2, …​, xn }, respectively.

5 in { 9, 5, 'cat', 'dog' }                 // -> true
4 in { 9, 5, 'cat', 'dog' }                 // -> false
'cat' in { 9, 5, 'cat', 'dog' }             // -> true
'mouse' notin { 9, 5, 'cat', 'dog' }        // -> true
'mouse' in { '*ous*', 'm*e' }               // -> true
'mice' in { '*ous*', 'm*e' }                // -> true

3.3.12. Multiplication

Infix * multiplies two numbers.

2 * 3           // -> 6
2 * pi          // -> 6.28...

3.3.13. Division

Infix / divides a number by another.

15 / 3              // -> 5
16 / 3              // -> 5.33...
3.14 / pi           // -> 0.999...
1 / 0               // -> 8.51e37
1 / -0.0            // -> -inf
When integer division has a remainder the result is promoted to floating point. Zero division results in a large floating point value except for the special case divisor -0.0 which produces -inf.

3.3.14. Modulo

Infix % yields the remainder after dividing two numbers.

15 % 3              // -> 0
16 % 3              // -> 1
3.14 % pi           // -> 3.14
6.2 % pi            // -> 3.06...
1 % 0               // -> 0
1.1 % 0             // -> 1.18e-38
1 % -0.0            // -> nan
Modulo zero results in 0 (or very close to 0) except for the special case divisor -0.0 which produces nan.

3.3.15. Addition

Infix + adds two numbers or concatenates a string with another object implicitly cast to string.

1 + 1               // -> 2
pi + e              // -> 5.8599...
'x' + 'y'           // -> "xy"
'x' + pi            // -> "x3.14159"

3.3.16. Subtraction

Infix - subtracts a number from another.

1 - 2               // -> -1
pi - e              // -> 0.42331...

3.3.17. Bitwise Shift

Infix << and >> perform left and right bit shift, respectively, of the integer representation of the left operand by the amount specified by the right operand. Bits are not rotated, i.e. once bits are shifted off the left or right edge they are discarded.

1 << 10             // -> 1024
1.9 << 10           // -> 1024
1.9 << 10.0         // -> 1.9
0xff >> 5           // -> 7
0xff >> 40          // -> 0
If the right operand is not an integer the shift operation is not performed.

3.3.18. Comparison

The infix comparison operators ==, !=, >, >=, <, <= compare the left and right operands yielding true or false. Operands may be numbers, strings, vertices or vectors.

'hello' == '*llo'           // -> true
'cat' != 'dog'              // -> true
5 > 4                       // -> true
5 >= 5.0                    // -> true
5.0 < 4.5                   // -> false
5 <= 4.9999999              // -> false
5 == 4.9999999              // -> true
Two floating point values compare equal when their difference is smaller than approximately 1.1921e-7. This affects == and !=, but not >= and <=.

3.3.19. Bitwise Logic

Infix operators &, | and ^ perform bitwise AND, OR and XOR, respectively, on their left and right operands.

0xf0 & 0x3c                 // -> 0x30
0xf0 | 0x3c                 // -> 0xfc
0xf0 ^ 0x3c                 // -> 0xcc

3.3.20. Logical Condition

Infix conditional operators && and || apply the logical conditions AND and OR, respectively, to their left and right operands.

1 == 2 && 2 > 1             // -> false
1 == 2 || 2 > 1             // -> true
1 == 2 && store( R1, 777 )  // -> false   (777 still stored in R1)
Logical short circuiting is not applied. All parts of an expression are always evaluated regardless of whether or not they contribute to the overall result of the expression.

3.3.21. Ternary Conditional

The infix ?: operator selects the second or third operand depending on the value of the first operand.

1 < 2 ? 3 + 4 : 5 + 6                      // -> 7
1 > 2 ? 3 + 4 : 5 + 6                      // -> 11
1 < 2 ? store( R1, 33 ) : store( R1, 44 )  // -> 33  (R1 contains 44)
1 > 2 ? store( R1, 33 ) : store( R1, 44 )  // -> 44  (R1 contains 44)
Logical short circuiting is not applied. All operations are completely evaluated, left to right, regardless of condition.

3.3.22. Expression definition

The assignment operator := associates a definition with a name that can be referenced later to invoke the expression. Expressions are normally assigned to names using pyvgx.Graph.Define(). However, it is also possible to dynamically assign expressions to names during query execution and then reference those names in other parts of the same query.

func := next['score'] / (10 * .arc.value)
quad := load( R2 ) ** 2 + load( R1 ) + vertex['offset']
Each graph instance has its own expression name scope and expressions cannot be shared between separate graph instances. Assigning an expression to a name will overwrite any previous assignment to that name.

3.3.23. Sub-expressions and Local Variables

An expression may contain one or more independent sub-expressions separated by semicolons ;. The last sub-expression determines the value of the overall expression. It is optional to terminate the last expression with a semicolon.

It is possible to assign the result of a sub-expression to a local variable. The scope of such a variable extends from the point of assignment until the end of the expression. Variable assignment uses the = operator, and must appear directly after the variable name at the start of a sub-expression. Variable names can be freely chosen so long as they conform to valid key syntax and do not conflict with pre-defined names or functions like pi, e, next, sqrt, etc.

1; 5 + log(10); 2.5;                          // -> 2.5
store(R1, 7); 3 + load( R1 );                 // -> 10
a = 2*pi; b = rad(360); a / b;                // -> 1.0
pi = 3.14;    //==>    Syntax Error: invalid variable assignment
Variable scope is local to the current evaluation of the expression. Once the last sub-expression has completed and returned its result all variables go out of scope. Use Evaluator Memory for global scope and to transfer data between different expressions or repeated evaluations of the same expression.

3.3.24. Label

The label operator : is exclusive to select statements, allowing processing of rendered result values to take place and making those values identifiable by the label.

val1: 1 / .arc.value; val2: next['score'] & 0xFFFF; val3: graph.ts
This syntax is only available in select= and the expression cannot be pre-defined.

3.3.25. Comma

The comma , is used to separate multiple objects such as function arguments, set members, and group elements.

max( next['score'], vertex['score'] )
next.id in { 'Alice', '*lie', 'B*' }
( .arc.value, inc( R1 ) ) * firstval( next['score'], 1.0 )
x = next['score'] * pi, y = 1 / .arc.value

3.3.26. Semicolon

The semicolon ; is used to terminate sub-expressions within a larger expression, and to separate fields or labeled expressions in select= statements.

/* Sub-Expression */
x = next['score'] / next.degree; y = log(x) + x/2; z = log2( y * x );

/* Select Statement */
.id; .arc.value; SCORE: next['score'] / 100.0; .degree

3.4. Attributes and Properties

Expressions may read information stored in arcs and vertices. Which arc or vertex is currently in scope for the evaluator depends on the query and how the graph is traversed.

Attributes and properties refer to two different classes of information associated with an object in the graph. An attribute is a built-in piece of information such as the arc type, arc value, vertex type, vertex degree, etc. A property is a custom key/value pair assigned to a vertex.

3.4.1. Neighborhood: next*

Attributes named next* refer to arcs ("Current Arc" column in Arc Attributes) that are traversed to reach the current anchor’s neighborhood, or to the contents of those neighbors ("Head" column in Vertex Attributes and Vertex Properties.)

When the attribute prefix is omitted it defaults to next. For example, .arc.value is equivalent to next.arc.value and .id is equivalent to next.id.

When used in select= the next prefix must be omitted, otherwise the field is interpreted as a vertex property.

For example, select=".arc.value; .id; next.degree" would interpret next.degree as a property of that name (i.e. next[ "next.degree" ].) This is because the simplified select= syntax allows a list of field names to be specified, where anything starting with a period . is an attribute and everything else is a property.

Labeled items in the select list are not subject to this restriction. For example, select=".arc.value; .id; DEG : next.degree" would evaluate next.degree as the vertex degree attribute and identify its value with the label "DEG" in the result.

3.4.2. Previous neighborhood: prev*

Attributes named prev* refer back to the arc ("Previous Arc" column in Arc Attributes) that was traversed to reach the current anchor, or to the contents of that arc’s initial vertex ("Previous Tail" column in Vertex Attributes and Vertex Properties.)

3.4.3. Current anchor: vertex*

Attributes names vertex* reference the current anchor ("Tail" column in Vertex Attributes and Vertex Properties.)

3.4.4. Example

graph.Neighborhood(
    "Alice",
    filter      = "next.arc.type == 'knows' && next['age'] >= 21",
    neighbor    = {
        'arc'       : D_OUT,
        'traverse'  : {
            'filter'    : "prev.arc.value < next.arc.value"
        }
    }
)

This query asks for all neighbors that Alice has a "knows" relationship to, whose age is at least 21 and also have connections to other neighbors that are stronger than Alice’s connection to them. (I.e Alice’s adult friends with better friends.)

Here "Alice" is the current anchor, next.arc.type is the relationship type of the arc from Alice to her neighbors, next['age'] is a property of Alice’s neighbors, prev.arc.value is the value of the arc from Alice to one of her neighbors, and next.arc.value is the value of the arc from one of her neighbors to their neighbors. Notice how in this example next.arc.type and next.arc.value reference different sets of arcs because they appear in two different expressions with different contexts.

3.4.5. Arc Attributes

Previous Arc Current Arc Type Description

prev.arc.type

next.arc.type
.arc.type

int

Relationship type enumeration

prev.arc.dir

next.arc.dir
.arc.dir

int (D_*)

Relationship direction

prev.arc.mod

next.arc.mod
.arc.mod

int (M_*)

Relationship value modifier

prev.arc.value

next.arc.value
.arc.value

int or float

Relationship value

prev.arc.dist

next.arc.dist
.arc.dist

int

Arc head’s distance from original query anchor vertex

prev.arc.issyn

next.arc.issyn
.arc.issyn

bool

Value is true if arc is a synthetic arc or false if arc is explicit. Explicit arcs are normal graph arcs. Synthetic arcs are produced as temporary arguments to expressions evaluated outside of arc traversal context.

prev.arc.isfwd

next.arc.isfwd
.arc.isfwd

bool

Value is true if arc is a forward-only arc, or false otherwise.

References to prev.arc* have meaning only when evaluated in a context where a traversal has already taken place. All prev.arc* attributes will be zero otherwise.
Expressions involving arc type equality checks may specify integer enumerations or string literals as the comparison operand. String literals are implicitly encoded as relationship type enumerations in this context. E.g. .arc.type == 'likes' is equivalent to .arc.type == relenc( 'likes' ). Note, however, that implicit encoding applies to string literals only. If the string operand is the result of a vertex property lookup it must be explicitly encoded for the match to work. E.g. .arc.type == vertex['relstr'] should instead be written as .arc.type == relenc( vertex['relstr'] ).
Wildcards are not supported in relationship type string literals. E.g. .arc.type == '*' and .arc.type == 'fr*' are always false.
Example: Alice’s close relationships
graph.Neighborhood(
    "Alice",
    filter    = ".arc.type in { 'friend', 'knows', 'likes' }",
    neighbor  = {
        'arc'    : D_IN,
        'filter' : "prev.arc.type == 'friend' || prev.arc.value > 100"
    }
)

3.4.6. Vertex Attributes

Tail (vertex) Head (next) Previous(prev) Type Description

vertex

next

prev

int

Vertex object, interpreted as the vertex address (integer) when used in mathematical expressions

vertex.id

next.id
.id

prev.id

vertexid

Vertex identifier

vertex.internalid

next.internalid
.internalid

prev.internalid

vertex

Vertex instance

vertex.type

next.type
.type

prev.type

int

Vertex type enumeration

vertex.deg

next.deg
.deg

prev.deg

int

Vertex degree

vertex.ideg

next.ideg
.ideg

prev.ideg

int

Vertex indegree

vertex.odeg

next.odeg
.odeg

prev.odeg

int

Vertex outdegree

vertex.vector

next.vector
.vector

prev.vector

vector

Vertex similarity vector

vertex.tmc

next.tmc
.tmc

prev.tmc

int

Vertex creation time.

vertex.tmm

next.tmm
.tmm

prev.tmm

int

Vertex modification time.

vertex.tmx

next.tmx
.tmx

prev.tmx

int

Vertex expiration time.

vertex.c1

next.c1
.c1

prev.c1

float

Vertex dynamic rank score 1st order coefficient.

vertex.c0

next.c0
.c0

prev.c0

float

Vertex dynamic rank score 0th order coefficient.

vertex.virtual

next.virtual
.virtual

prev.virtual

int

1 if vertex is virtual, 0 if vertex is real

vertex.locked

next.locked
.locked

prev.locked

int

True when vertex is locked writable by another thread

vertex.address

next.address
.address

prev.address

int

Vertex memory address

vertex.index

next.index
.index

prev.index

int

Vertex object location index based on its memory address

vertex.bitindex

next.bitindex
.bitindex

prev.bitindex

int

Vertex bitvector quadword offset

vertex.bitvector

next.bitvector
.bitvector

prev.bitvector

bitvector

Vertex bitvector quadword

vertex.op

next.op
.op

prev.op

int

Operation id of the last modifying graph operation for this vertex.

vertex.refc

next.refc
.refc

prev.refc

int

Vertex object reference count

vertex.bidx

next.bidx
.bidx

prev.bidx

int

Vertex object allocator block index

vertex.oidx

next.oidx
.oidx

prev.oidx

int

Vertex object allocator block offset

vertex.handle

next.handle
.handle

prev.handle

int

Numeric vertex identifier (process independent) with 42 significant bits, in the range 2199023255552 - 3298534883327.

vertex.enum

next.enum
.enum

prev.enum

int

Numeric vertex identifier (process independent) with 31 significant bits, in the range 0 - 2147483647. (May evaluate to -1 in large graphs, if so use .handle)

References to prev* have meaning only when evaluated in a context where a traversal has already taken place. All prev* attributes are undefined otherwise.
Example: Alice’s well-connected friends with first initial "B"
graph.Neighborhood(
    "Alice",
    arc       = ("friend", D_OUT),
    filter    = "next.deg > 100 || next.ideg > 70",
    neighbor  = {
        'filter'   : "vertex.id == 'B*'"
    }
)

3.4.7. Vertex Properties

Tail (vertex) Head (next) Previous (prev) Description

vertex.propcount

next.propcount
.propcount

prev.propcount

Number of vertex properties

vertex[ key ]

next[ key ]

prev[ key ]

Value of vertex property key if it exists. The default value is 0 when property lookup occurs within arithmetic expressions. The default value is nan in all other contexts.

vertex.property( key[, default[, condition]] )

next.property( key[, default[, condition]] )
.property( key[, default[, condition]] )

prev.property( key[, default[, condition]] )

Same as subscript syntax when only key is given. If specified, default value is produced when property does not exist. If condition is also specified the lookup takes place only if condition is true, otherwise default is produced.

Accessing prev[ key ] or prev.property( key, …​ ) has meaning only when evaluated in a context where a traversal has already taken place. The lookup will always return nan otherwise.
Example: Alice’s well-connected friends who are 20 - 29 years old
graph.Neighborhood(
    "Alice",
    arc       = ("friend", D_OUT),
    filter    = "next.deg > 100 || next.ideg > 70",
    neighbor  = {
        'filter'   : "vertex['age'] in range( 20, 30 )"
    }
)
Be careful when writing filters that involve access to head vertex attributes or properties. There is a significant performance penalty when dereferencing a vertex object, especially when traversing large neighborhoods. Even if your filter includes a condition on the arc designed to reduce the number of candidates the entire expression is still evaluated leading to unnecessary overhead.

The problem is easily avoided by splitting the expression into separate filters and using the arc=(…​) and neighbor={…​} parameters, ensuring that expensive operations are only executed after passing less expensive filters. As a rule, avoid mixing quick conditions with expensive conditions within the same filter. Arc conditions and current vertex attribute conditions are quick. Head vertex conditions are expensive, especially when property lookup is involved.

As in the example above, the arc condition is placed in the arc= parameter and therefore evaluated first, avoiding premature execution of next.deg > 100 || next.ideg > 70. Furthermore, the property condition vertex['age'] in range( 20, 30 ) is deferred until the very last by placing it in a separate filter within neighbor={…​}.
Since vertex properties are costly to store and reference, in some cases you may consider using the general purpose c0 and c1 numeric attributes. Although primarily intended for use with certain dynamic ranking functions, you are free to "abuse" them as light-weight numeric (single precision float) properties.

3.5. Synthetic Arcs

Synthetic arcs are virtual arcs generated by evaluator expressions to enable filtering, collection and traversal based on more than one arc value.

3.5.1. Background and Default Behavior

Two vertices A and B may be connected with one or more arcs AB = { ab1, ab2, …​ }. When AB contains a single component arc ab1 (or just ab) we call AB a simple arc. When AB contains two or more component arcs we call AB a multiple arc.

When traversing arc AB each component arc abi is processed separately without regard to any other component arc in AB. If a second level of traversal of B's neighborhood is performed then B's neighborhood will be traversed repeatedly each time B is visited as a result of traversing a component arc in AB.

For example, if A is connected to B with multiple arc AB = { ab1, ab2, ab3 } and B is connected to C with multiple arc BC = { bc1, bc2, bc3 } then there are 3 paths from A to B and 3*3=9 paths from A to C. The default query behavior is to exhaustively traverse all unique paths.

This query returns ab1, ab2, ab3 plus all nine combinations of (abi, bcj) for a total of 12 arcs/paths.
graph.Neighborhood(
    id        = "A",
    fields    = F_AARC,
    collect   = C_COLLECT,
    neighbor  = {
        'arc'    : D_OUT,
        'collect': C_COLLECT
    }
) # -> list of 12 entries

3.5.2. Arc Collapsing with Synthetic Arc Operators

Synthetic arcs are helpful when the default behavior is not desired. A synthetic arc absyn is a simple arc substitute for arc AB (simple or multiple) between two vertices A and B.

Synthetic arcs are not stored in the graph. They are temporary virtual relationships created by the expression evaluator for the purpose of filtering, traversal and collection.

3.5.2.1. Value Capture and Synthetic Arc Evaluation

A filter expression containing one or more Synthetic Arc Operators triggers synthetic arc mode for the expression evaluator. In synthetic arc mode one additional evaluation occurs after all component arcs abi in AB have been processed. The additional evaluation at the end is performed for synthetic arc absyn.

When evaluating component arcs abi all synthetic arc operators (except '*.arc.issyn') capture arc information into temporary registers and return null. When evaluating synthetic arc absyn the synthetic arc operators return the previously captured values. Most expressions involving null operands are no-ops, making it possible to write the expression as if AB were a dictionary with simultaneous access to all of its component arcs abi.

See Example 1: Conditional Collection of Composite Value for a detailed description of how information is captured and used.

3.5.2.2. Synthetic Arc Attributes

Synthetic arcs have the special reserved relationship name "__synthetic__" and they have no value and no modifier unless a value is assigned with collect() or collectif(). When a value is assigned the modifier is automatically set to M_INT or M_FLT depending on the value type.

3.5.2.3. Synthetic Arc Operators
Table 3. Synthetic Arc Operators
Operator Return Value abi Capture Phase Synthetic Arc absyn

.arc.issyn
next.arc.issyn
prev.arc.issyn

true or false

Evaluate to false when processing component arcs abi

Evaluate to true when processing synthetic arc absyn

synarc.value( rel, mod )

null, real, or integer

Capture the value of component arc abi matching relationship rel and modifier mod. The captured value is stored in a hidden temporary register. Return null.

Return the value of component arc abi matching relationship rel and modifier mod. If no match was found null is returned.

synarc.hasrel( rn-1, …​, ri, …​, r0 )

null or bitvector

Set bit i in temporary hidden bitvector V to 1 if relationship ri exists in arc AB. Maximum n is 64. Leftmost arguments n and higher are ignored if n > 64.

Return bitvector V. Bits are set to 1 in positions corresponding to relationships in the argument list that exist in arc AB. All other bits are 0.

synarc.hasmod( mn-1, …​, mi, …​, m0 )

null or bitvector

Set bit i in temporary hidden bitvector V to 1 if modifier mi exists in arc AB. Maximum n is 64. Leftmost arguments n and higher are ignored if n > 64.

Return bitvector V. Bits are set to 1 in positions corresponding to modifiers in the argument list that exist in arc AB. All other bits are 0.

synarc.hasrelmod( rn-1, mn-1, …​, r0, m0 )

null or bitvector

Set bit i in temporary hidden bitvector V to 1 if a component arc in AB with relationship ri and modifier mi exists. Maximum n is 64. Leftmost arguments n and higher are ignored if n > 64. An even number of arguments is required.

Return bitvector V. Bits are set to 1 in positions corresponding to relationship/modifier pairs in the argument list that match a component arc in AB. All other bits are 0.

synarc.decay( rel[, rate] ) [[synarcdecay]]
synarc.xdecay( _rel
[, rate] )

null or real

Collect relevant values and time information from components arcs abi with relationship rel. Return null.

Return a real value representing a decayed version of relationship rel's initial value. See synarc.decay() and synarc.xdecay() for a detailed description.

3.5.3. Synthetic Arcs Examples

3.5.3.1. Example 1: Conditional Collection of Composite Value

Consider vertex A connected to two terminals X and Y with arcs AX = { ('count', M_CNT, 25), ('weight', M_FLT, 4.0 ) }, and AY = { ('count', M_CNT, 20), ('weight', M_FLT, 2.0) }. A filter expression can be written to conditionally collect synthetic arcs rather than individual component arcs.

Conditionally collect synthetic arc based on composite value
graph.Neighborhood(
    id        = "A",
    fields    = F_AARC,
    collect   = C_SCAN,
    filter    = """
                c = synarc.value('count',M_CNT);
                w = synarc.value('weight',M_FLT);
                score = c * w;
                collectif( score < 50.0, score );
                """
) # -> ['( A )-[ __synthetic__ <M_FLT> 40.000 ]->( Y )']

The filter expression is evaluated three times for (multiple) arcs AX and AY. First consider the processing of AX:

First filter iteration for AX

The neighborhood query visits component arc ax1 = ('count', M_CNT, 25). The sub-expression c = synarc.value('count',M_CNT); has a match on ax1 and captures the value 25 into an internal register. The value null is returned and assigned to c. The sub-expression w = synarc.value('weight',M_FLT); does not match ax1 and is a no-op. The value null is returned and assigned to w. The sub-expression score = c * w; assigns null to score since both factors are null. The sub-expression collectif( score < 50.0, score ); is a no-op because score < 50.0 is false when score is null. (Comparisons involving null are always false, except when testing equality between two null values.)

Second filter iteration for AX

The neighborhood query visits component arc ax2 = ('weight', M_FLT, 4.0). Evaluation is the same as for ax1 as above, except sub-expression w = synarc.value('weight',M_FLT); now matches ax2 and captures the value 4.0 into another internal register.

Third filter iteration for AX

The neighborhood query now invokes the evaluator one final time for synthetic arc axsyn = ('__synthetic__', M_NONE, 0). The sub-expression c = synarc.value('count',M_CNT); assigns 25 to c as this value is retrieved from the internal register that previously captured this value from ax1. The sub-expression w = synarc.value('weight',M_FLT); assigned 4.0 to w as this value is retrieved from the internal register that previously captured this value from ax2. The sub-expression score = c * w; multiplies the two values and assigns 100.0 to score; The sub-expression collectif( score < 50.0, score ); is a no-op since the condition is not met. The arc AX did not match and is not collected.

First filter iteration for AY

Component arc ay1 = ('count', M_CNT, 20) matches and the value 20 is captured, in the same manner as described above for ax1.

Second filter iteration for AY

Component arc ay2 = ('weight', M_FLT, 2.0) matches and the value 2.0 is captured, in the same manner as described above for ax2.

Third filter iteration for AY

Synthetic arc aysyn = ('__synthetic__', M_NONE, 0) is processed, assigning c = 20 and w = 2.0 in the same manner as described above for axsyn. In this case score = 40.0 and collectif( score < 50.0, score ); therefore collects the synthetic arc aysyn with a value 40.0 provided by the second argument to collectif(). We have now built and collected a single composite arc ('__synthetic__', M_FLT, 40.0) based on components of AY.

3.5.3.2. Example 2: Avoiding Exhaustive Path Traversal using Arc Collapsing

Consider a graph connecting a root vertex to many neighbors with wide multiple arcs, which in turn are connected to many neighbors with wide multiple arcs. This graph has a very large number of unique paths from the root to each vertex in the 2nd level neighborhood.

Formally, such a graph can be described as follows. Root vertex A has n terminals B = { B1, …​ Bn }, where each Bi has m terminals Ci = { Ci,1, …​, Ci,m }. A connects to each Bi with k-multiple arc ABi = { abi,1, …​, abi,k } and Bi connects to each Ci,j with q-multiple arc BCi,j = { bci,j,1, …​, bci,j,q}.

Build graph connecting A via B to C with wide multiple arcs
def build( n=100, m=100, k=10, q=10 ):
    g = Graph( "levels" )
    A = g.NewVertex( "A" )
    for i in range( 1, n+1 ):
        Bi = g.NewVertex( "B_%d" % i )
        for ab in range( 1, k+1 ):
            g.Connect( A, ("r%d"%ab, M_FLT, i*ab), Bi )
        for j in range( 1, m+1 ):
            Cij = g.NewVertex( "C_%d_%d" % (i,j) )
            for bc in range( 1, q+1 ):
                g.Connect( Bi, ("r%d"%bc, M_FLT, i*ab*j*bc), Cij )
    return g

If n = m = 1000 and k = q = 100, then A has 1000 terminals B1 through B1000 (each connected via a 100-multiple arc) and each Bi has 1000 terminals Ci,1 through Ci,1000 (each connected via a 100-multiple arc.)

graph = build( n=1000, m=1000, k=100, q=100 )

The following query collects all paths from A to all C, yielding a large result set with n * k * m * q = 10,000,000,000 arcs.

Do not run this query!
Exhaustive path traversal from A to C
graph.Neighborhood(
    id        = "A",
    fields    = F_AARC,
    collect   = C_SCAN,
    neighbor  = {
        'traverse': {
            'arc'    : D_OUT,
            'collect': C_COLLECT
        }
    }
) # -> list of n*k*m*q=10,000,000,000 arcs

We can use synthetic arcs to avoid exhaustive path traversal. By adding filter conditions that check for .arc.issyn == true we can restrict traversal and collection to synthetic arcs:

Collapsed path traversal from A to C
graph.Neighborhood(
    id        = "A",
    fields    = F_AARC,
    collect   = C_SCAN,
    filter    = ".arc.issyn",
    neighbor  = {
        'traverse': {
            'arc'    : D_OUT,
            'collect': C_COLLECT,
            'filter' : ".arc.issyn"
        }
    }
) # -> list of n*m arcs=1,000,000 arcs

We can also use a combination of synthetic arcs and value aggregation to collect collapsed arcs with computed values. The query below uses memory registers to accumulate values. Register R1 accumulates the sum of values of arcs { abi,1, …​, abi,k }. Register R2 accumulates the sum of values of arcs { bci,j,1, …​, bci,j,q }.

Fancy collapsed path traversal from A to C
graph.Neighborhood(
    id         = "A",
    sortby     = S_VAL,
    fields     = F_AARC,
    collect    = C_SCAN,
    pre        = """
                 /* Init AB and BC value accumulators */
                 do( store(R1,0),store(R2,0) )
                 """,
    filter     = """
                 // Accumulate AB_i
                 add(R1,.arc.value);
                 // Traverse to B for synthetic arc only
                 .arc.issyn
                 """,
    neighbor   = {
        'traverse' : {
            'arc'     : D_OUT,
            'collect' : C_SCAN,
            'filter'  : """
                        // Accumulate BC_ij and continue
                        add(R2,.arc.value);
                        returnif(!.arc.issyn,0);
                        // Synthetic arc with computed value
                        score = load(R1) + load(R2);
                        collect(score);
                        // Reset BC_ij accumulator
                        do(store(R2,0));
                        """
        },
        'post'     : """
                     // Reset AB_i accumulator
                     do(store(R1,0))
                     """
    }
)

3.6. Constants

Many numeric constants are available for use in expressions. These are summarized below.

3.6.1. Null

Name Type Value Description

null

null

No value

Example: Non-existent vertex attributes
graph.CreateVertex( "V" )
graph.Evaluate( "vertex['prop1'] == null", head="V" ) # -> 1

3.6.2. Boolean

Name Type Value Description

true

int

1

Boolean True

false

int

0

Boolean False

Example: Boolean values are integers
graph.Evaluate( "true == 1 && false == 0" )  # -> 1

3.6.3. Timestamp Limits

Name Type Value Description

T_NEVER

int

4102444800

Never

T_MAX

int

4102444799

Thu Dec 31 23:59:59 2099

T_MIN

int

1

Thu Jan 01 00:00:01 1970

Example: Alice’s connections that will expire at some point
graph.Neighborhood(
    "Alice",
    filter = ".arc.mod == M_TMX && .arc.value < T_NEVER"
)

3.6.4. Arc Direction

Name Type Value Description

D_ANY

int

0

Any arc direction

D_IN

int

1

Inbound arc direction

D_OUT

int

2

Outbound arc direction

D_BOTH

int

3

Bi-directional arc

Example: Sort by arc value, boosting inbound arcs by 1000 extra points
graph.Neighborhood(
    "Alice",
    arc    = D_ANY,
    rank   = "1000 * (.arc.dir == D_IN) + .arc.value",
    sortby = S_RANK,
    select = ".arc.dir; .rank"
)
The .arc.dir attribute is "observational" only and cannot be used to control traversal direction. Traversal direction is controlled by the arc= parameter and .arc.dir reflects the current direction. This implies that .arc.dir will never equal D_ANY. Furthermore, in filter= expressions .arc.dir will either be D_IN or D_OUT, never D_BOTH. However, in rank= and select= expressions .arc.dir may equal D_BOTH if a bi-directional relationship was collected. This subtle point warrants another example:
Example: Sort bi-directional arcs before other arcs
graph.Neighborhood(
    "X",
    rank     = ".arc.dir == D_BOTH",
    sortby   = S_RANK,
    neighbor = {
        'collect'  : C_COLLECT,
        'arc'      : D_BOTH
    },
    select   = ".arc.dir"
)

3.6.5. Arc Modifiers

Name Type Value Description

M_ANY

int

0

No modifier / wildcard

M_STAT

int

1

Static relationship (no value)

M_SIM

int

18

Similarity relationship, explicit score

M_DIST

int

19

Generic distance relationship value

M_LSH

int

4

LSH bit pattern subject to hamming distance filter

M_INT

int

5

General purpose integer relationship value

M_UINT

int

6

General purpose unsigned integer relationship value

M_FLT

int

23

General purpose floating point relationship value

M_CNT

int

8

Automatic counter relationship value

M_ACC

int

25

Automatic accumulator relationship value

M_TMC

int

12

Arc creation time relationship value

M_TMM

int

13

Arc modification time relationship value

M_TMX

int

14

Arc expiration time relationship value

Example: Match multiple arc modifiers
graph.Neighborhood(
    "Alice",
    filter = ".arc.mod in { M_INT, M_FLT } && .arc.value > 10"
)

3.6.6. Memory Registers

Name Type Value Description

R1

int

-1

Evaluator Memory register #1

R2

int

-2

Evaluator Memory register #2

R3

int

-3

Evaluator Memory register #3

R4

int

-4

Evaluator Memory register #4

Example: Sort higher when arc and neighbor’s arc have similar values
graph.Neighborhood(
    "Alice",
    filter   = "store( R1, .arc.value ) in range( 1, 100 )",
    neighbor = {
        'arc'     : D_OUT,
        'traverse': {
            'filter'  : "store( R2, .arc.value ) in range( 1, 100 )"
        }
    },
    rank     = "prox( load(R1), load(R2) )",
    sortby   = S_RANK,
)

3.6.7. Collector Staging Slots

Name Type Value Description

C1

int

0

Collector staging slot #1

C2

int

1

Collector staging slot #2

C3

int

2

Collector staging slot #3

C4

int

3

Collector staging slot #4

Example: Find the most visited country for each of Alice’s friends.
graph.Neighborhood(
    "Alice",
    collect  = C_NONE,
    neighbor = {
        'type'     : "person",
        'filter'   : "do( store( R1, 0 ) )",
        'traverse' : {
            'arc'     : ( "visit", D_OUT ),
            'collect' : C_SCAN,
            'filter'  : """
                        stageif(
                            storeif(
                                next.arc.value > load(R1),
                                R1,
                                next.arc.value
                            ),
                            null,
                            C1
                        )
                        """,
            'neighbor': {
                'type'  : "country"
            }
        },
        'post'     : "commitif( load(R1) > 0, C1 )"
    }
)

3.6.8. Mathematical Constants

Name Type Value Description

pi

float

3.14159265358979323846

\( \pi \)

e

float

2.71828182845904523536

\( e \)

root2

float

1.4142135623730950488

\( \sqrt{ 2 } \)

root3

float

1.73205080756887729353

\( \sqrt{ 3 } \)

root5

float

2.23606797749978969641

\( \sqrt{ 5 } \)

phi

float

1.6180339887498948482

\( \varphi = \frac{1+\sqrt{5}}{2} \) (golden ratio)

zeta3

float

1.2020569031595942854

\( \zeta(3) = \displaystyle \sum_{n=1}^\infty \frac{1}{n^3} \) (Apéry’s constant)

graph.Evaluate( "(e + root2) / pi" ) # -> 1.3154...

3.7. Current Environment

These attributes are relative to the current execution environment.

3.7.1. Current Graph State

Attribute Type Description

graph.ts

float

Current graph time, in seconds since 1970

graph.t0

float

Graph inception time

graph.age

float

Graph age = graph.ts - graph.t0

graph.order

int

Number of vertices in graph

graph.size

int

Number of arcs in graph

graph.op

int

Current operation counter

Graph state attributes are sampled the first time an expression is evaluated in a query and remain constant for the duration of the query.
Example: Derive information from graph attributes
graph.Evaluate( "graph.size / graph.order" ) # -> average outdegree
graph.Evaluate( "graph.ts - graph.age" )     # -> graph creation time

3.7.2. System Attributes

Attribute Type Description

sys.tick

int

System tick in nanoseconds since computer boot time

sys.uptime

float

System uptime in seconds

System attributes are re-read from their source each time an expression is evaluated.
Example: Perform deep traversal and return results in the order of most time consuming
graph.Neighborhood(
    "A",
    filter   = "store(R1, sys.tick)",
    neighbor = {
        'arc'      : D_OUT,
        'traverse' : {
            'neighbor' : {
                'traverse' : {
                    'filter' : ".id == '*z*'"
                }
            }
        }
    },
    rank     = "sys.tick - load(R1)",
    sortby   = S_RANK,
    select   = ".rank"
)

3.7.3. Current Evaluator Context

Name Type Description

context.rank
.rank

float

In select= statements this is the result sorting value as determined by the sortby= parameter.

In filter= or rank= expressions this value is normally 0. However, when the expression is evaluated in the context of traversal or collection resulting from breadth-first search with mcull() this value is set to the score of the culled item being processed.

vector

vector

Similarity probe vector as supplied in the query vector= parameter or similarity filter.

collectable.real

float

An item’s rank score must be greater than (less than) this value to have a chance of being included in a query result sorted by a floating point field in descending (ascending) order.

NOTE: Query sortby parameter determines the data type. Use collectable.real with sortby constants S* whose Type is (or may be) float.

collectable.int

int

An item’s rank score must be greater than (less than) this value to have a chance of being included in a query result sorted by an integer field in descending (ascending) order.

NOTE: Query sortby parameter determines the data type. Use collectable.int with sortby constants S* whose Type is int.

Example: Sort results by length of neighbor’s id and render result by custom label
graph.Neighborhood(
    "X",
    rank   = "len( .id )",
    sortby = S_RANK,
    select = "IDLEN: int( .rank )"
)

3.8. Functions

This section summarizes built-in functions available for use in expressions.

3.8.1. Mathematical Functions

Function Description Comment

inv( x )

\( y = \displaystyle \frac{1}{x} \)

NOTE: inv(0) is defined as 3.4·1038.

neg( x )

\( y = -x \)

log2( x )

\( y = \log_2 x \)

NOTE: x ≤ 0 → −1074

log( x )

\( y = \ln{x} \)

NOTE: x ≤ 0 → −745

log10( x )

\( y = \log_{10} x \)

NOTE: x ≤ 0 → −324

rad( x )

\( y = \displaystyle \frac{\pi x}{180} \)

Degrees to radians

deg( x )

\( y = \displaystyle \frac{180 \cdot x}{\pi} \)

Radians to degrees

sin( x )

\( y = \sin x \)

cos( x )

\( y = \cos x \)

tan( x )

\( y = \tan x \)

asin( x )

\( y = \arcsin x \)

acos( x )

\( y = \arccos x \)

atan( x )

\( y = \arctan x \)

atan2( a, b )

\( y = \displaystyle \arctan { \frac{a}{b} } \)

Both signs of a and b are used to determine the correct quadrant.

sinh( x )

\( y = \sinh x \)

cosh( x )

\( y = \cosh x \)

tanh( x )

\( y = \tanh x \)

asinh( x )

\( y = \arcsinh x \)

acosh( x )

\( y = \arccosh x \)

atanh( x )

\( y = \arctanh x \)

sinc( x )

\( y = \displaystyle \frac{sin (\pi x)}{\pi x} \)

exp( x )

\( y = \displaystyle e^x \)

abs( x )

\( y = | x | \)

sqrt( x )

\( y = \sqrt x \)

NOTE: The result will be 0 when x is negative

ceil( x )

\( y = \lceil x \rceil \)

floor( x )

\( y = \lfloor x \rfloor \)

round( x )

\( y = \lfloor x \rceil \)

NOTE: Rounding rule is to round away from zero. (Differs from Python3 round-to-even rule!)

sign( x )

\( y = \begin{cases} 1 & \quad \text{if } x > 0 \\ 0 & \quad \text{if } x = 0 \\ -1 & \quad \text{if } x < 0 \end{cases} \)

fac( x )

\( y = x! \)

popcnt( x )

Return the number of bits set to 1 in the binary representation of x

comb( n, k )

\( y = \displaystyle \binom{n}{k} = \frac{n!}{k!(n-k)!} \)

max( a, b )

\( y = \begin{cases} a & \quad \text{if } a \geq b \\ b & \quad \text{if } a < b \end{cases} \)

min( a, b )

\( y = \begin{cases} a & \quad \text{if } a < b \\ b & \quad \text{if } a \geq b \end{cases} \)

hash( x )

Return a 64-bit integer hash value \( y = h(x) \)

random()

Return a random number \( y \in [0, 1] \)

randbits()

Return a random bitvector

randint( a, b )

Return a random integer \(y \in [a, b \rangle \)

Example: Miscellaneous function calls
graph.Evaluate( "log( 5 ) - sqrt( 17 )" ) # -> -2.51...
graph.Evaluate( "acos( sin( pi / 3 ) )" ) # -> 0.524...
graph.Evaluate( "sqrt( log( exp(4) ) )" ) # -> 2.0
graph.Evaluate( "hash( 'helloworld!' )" ) # -> 7982852363355633418

3.8.2. Type Cast and Checks

Operation Description

int( x )

Return the integer representation of object x. If x is a non-numeric object with a memory address the address is returned. If x is real inf (or -inf) the maximum (or minimum) integer value is returned. If x is the result of a non-existent property lookup 0 is returned. If x is any other object x is returned unchanged.

intr( x )

Same as int() except x is rounded to the nearest whole number before the cast if x is real.

asint( x )

Reinterpret the bits of object x as an integer. If x is real (including inf and nan) the bits are reinterpreted directly as a 64-bit signed integer. If x is a string representing a number it is reinterpreted as the corresponding integer. If the string does not represent a number 0 is returned. If x is a vector it is interpreted as the vector’s length. If x is any other object with a memory address the address is returned. If x is the result of a non-existent property lookup 0 is returned. If x is any other object x is returned unchanged.

asbits( x )

Return the raw bits of object x with type set to int.

real( x )

Return the floating point representation of object x. If x is a non-numeric object with a memory address the address is returned as a floating point number. If x is int the corresponding floating point value is returned. If x is the result of a non-existent property lookup 0.0 is returned. If x is any other object (including real) x is returned unchanged.

asreal( x )

Reinterpret the bits of object x as a floating point number. If x is int the bits are reinterpreted directly as a double precision floating point value. If x is real inf (or -inf) the maximum (or minimum) double value is returned. If x is a string representing a number it is reinterpreted as the corresponding floating point value. If the string does not represent a number nan is returned. If x is a vector it is interpreted as the vector’s magnitude. If x is any other object with a memory address the address is returned as a floating point number. If x is the result of a non-existent property lookup 0.0 is returned. If x is any other object x is returned unchanged.

str( x )

Return the string representation of object x.

NOTE: The returned string is a new string object stored in the Evaluator Memory object used by the expression. Strings produced by this function are not deallocated until the memory object is deleted or explicitly cleared using "mreset()" in an expression or calling Reset() on the associated Python object.

bitvector( x )

Return the raw bits of object x with type set to bitvector.

isint( x )

Return 1 if x is an integer, otherwise return 0

isreal( x )

Return 1 if x is a floating point value, otherwise return 0

isstr( x )

Return 1 if x is a string object, otherwise return 0

isvector( x )

Return 1 if x is a vector object, otherwise return 0

isbitvector( x )

Return 1 if x is a bitvector, otherwise return 0

isnan( x )

Return 1 if x is real nan or any object other than integer, bitvector or floating point, otherwise return 0

isinf( x )

Return 1 if x is real inf or -inf, otherwise return 0

isarray( x )

Return 1 if x is a numeric array type, otherwise return 0. See Vertex Property Types: list for how to assign a property for which isarray() is true.

ismap( x )

Return 1 if x is a map of keyval items, otherwise return 0. See Vertex Property Types: dict for how to assign a property for which ismap() is true.

iskeyval( x )

Return 1 if x is a keyval type, otherwise return 0

isbytearray( x )

Return 1 if x is an array of bytes (0-255), otherwise return 0. See Vertex Property Types: bytearray for how to assign a property for which isbytearray() is true.

isbytes( x )

Return 1 if x is an array of bytes (0-255), otherwise return 0. See Vertex Property Types: bytes for how to assign a property for which isbytes() is true.

len( x )

Return the length of object x. Definition of length depends on object type.

Length of string and vertexid is number of UTF-8 bytes in encoded string.
Length of vector is number of dimensions.
Length of bitvector is number of bits set to 1.
Length of bytearray and bytes is number of bytes.
Length of dict and list is number of items.
Length of any other object is 0.

Example: Miscellaneous casts and checks
graph.Evaluate( "int( 2.5 )" )              # -> 2
graph.Evaluate( "asint( 2.5 )" )            # -> 0x4004000000000000
graph.Evaluate( "asint( '123' )" )          # -> 123
graph.Evaluate( "real( 2 )" )               # -> 2.0
graph.Evaluate( "asreal( 0x4004 << 48 )" )  # -> 2.5
graph.Evaluate( "bitvector( 0xCC )" )       # -> 0xCC
graph.Evaluate( "isinf( -1/-0.0 )" )        # -> 1
graph.Evaluate( "len( 'hello' )" )          # -> 5

3.8.3. String Functions

Operation Return Value New Str Description

strcmp( a, b )

Integer

Compare strings a and b. Return zero if strings are equal, negative if a compares less than b, or positive if a compares greater than b. If either a or b is not a string, null is returned.

strcasecmp( a, b )

Integer

Compare strings a and b, ignoring case. Return zero if strings are equal, negative if a compares less than b, or positive if a compares greater than b. If either a or b is not a string, null is returned.

strlen( x )

Integer

Return the number of bytes in string x. If x is not string or vertexid 0 is returned.

startswith( s, p )

0 or 1

Return 1 if string s starts with string p, otherwise return 0. If either a or b is not a string, null is returned.

endswith( string, probe )

0 or 1

Return 1 if string s ends with string p, otherwise return 0. If either a or b is not a string, null is returned.

join( sep, s1, s2, …​, sn )

s1 sep s2 sep …​ sep sn

y

Concatenate any number of strings si with string sep inserted between each. Returns a new string object. If sep or si are not strings they are implicitly cast using str().

replace( str, probe, repl )

Modified copy of str

y

Return a new string object copied from str with all occurrences of probe replaced with repl. All non-string arguments are implicitly cast using str().

idx( s, i )

Integer or real

Return the value at index i of string s. For normal strings this is the byte value at offset i. For a vertex property numeric array this is the numeric value at offset i.

slice( str, a, b )

Substring of str

y

Return a new string object representing the substring of str starting at index a and ending at index b - 1. If str is not a string object it is implicitly cast using str(). Arguments a and b must be integers or null, which is interpreted as 0 and -1 for a and b, respectively.

For those functions returning strings, each time the function is evaluated a new string is created and stored in the Evaluator Memory object used by the expression. Strings are not deallocated until the memory object is deleted or explicitly cleared using "mreset()" in an expression or calling Reset() on the associated Python object.
It is also possible to concatenate strings using the infix ` operator. As long as the first operand is a string object all subsequent infix operands added with ` are implicitly cast using str().
Example: String functions
graph.Evaluate( "strcmp( 'hello', 'hi' )" )          # -> -1
graph.Evaluate( "strcasecmp( 'Hello', 'hello' )" )   # -> 0
graph.Evaluate( "startswith( 'Hello', 'He' )" )      # -> 1
graph.Evaluate( "endswith( 'Hello', 'lo' )" )        # -> 1
graph.Evaluate( "join( ' ', 'a', 'b', 'c' )" )       # ->  "a b c"
graph.Evaluate( "replace( 'say soda', 's', 'y' )" )  # ->  "yay yoda"
graph.Evaluate( "slice( 'hello', 1, 4 )" )           # ->  "ell"
graph.Evaluate( "str(pi)")                           # ->  "3.14159"
graph.Evaluate( "'hello' + 1 + 2")                   # ->  "hello12"
graph.Evaluate( "''+sin(1)+sin(0)" )                 # ->  "0.8414710.00000"

3.8.4. Property Lookup

Function Description

vertex.property( x[, default[, cond ]] )

Return property x of current tail vertex. If the property does not exist and no default is given, the return value depends on context: In infix arithmetic context the non-existent property evaluates to 0. In all other contexts the non-existent property evaluates to nan.

When default is provided it is returned if the property does not exist.

When condition cond is provided the lookup only takes place if cond is true. If cond is false OR the property does not exist, default is returned.

TIP: Conditional lookup can be used to avoid unnecessary memory access during evaluation and can speed up processing significantly in some cases. Because of No Short Circuiting unconditional property lookup is executed regardless of whether or not the final value of the expression depends on it.

next.property( x[, default[, cond ]] )
.property( x[, default[, cond ]] )

Same as vertex.property() except lookup occurs in current head vertex.

prev.property( x[, default[, cond ]] )

Same as vertex.property() except lookup occurs in previous tail vertex.

Example: Property lookup
V = g.NewVertex( "V" )
V['x'] = 100
g.Evaluate( "vertex.property( 'x' )", tail=V )                # -> 100
g.Evaluate( "vertex.property( 'y', 999 )", tail=V )           # -> 999
g.Evaluate( "next.property( 'x', 0, next.deg > 0 )", head=V ) # -> 0

Notice how the 3rd property lookup does not perform any lookup unless the degree is non-zero. If evaluated for millions of arcs where the condition is mostly false the memory load is reduced and the query runs faster.

3.8.5. Enumeration

Function Argument Type Return Value Description

relenc( x )

str

int

Return the internal enumeration code for relationship x. If x is an integer then x is returned, otherwise if x is not a string 0 is returned.

typeenc( x )

str

int

Return the internal enumeration code for vertex type x. If x is an integer then x is returned, otherwise if x is not a string 0 is returned.

reldec( x )

int

str

Return the relationship string for internal enumeration code x. The default string "__UNKNOWN__" is returned if integer x is not a valid enumeration code. If x is not an integer x is returned.

typedec( x )

int

str

Return the vertex type string for internal enumeration code x. The default string "__UNKNOWN__" is returned if integer x is not a valid enumeration code. If x is not an integer x is returned.

modtostr( x )

M_*

str

Return the string representation of arc modifier constant x. If x is not a valid modifier constant the result is undefined.

dirtostr( x )

D_*

str

Return the string representation of arc direction constant x. If x is not a valid direction constant the result is undefined.

Example: Miscellaneous enumerations
graph.Evaluate( "modtostr( 5 )" )      # -> "M_INT"
graph.Evaluate( "relenc( 'likes' )" )  # -> 3229  (may vary!)
graph.Evaluate( "reldec( 3229 )" )     # -> "likes"

3.8.6. Similarity (Feature Vectors)

Function Argument Type Return Value Return Type Description

sim( v1, v2 )

vector

\( s \in \lbrack 0, 1 \rbrack \)

float

Compute the similarity score s for vectors v1 and v2.

cosine( v1, v2 )

vector

\( c \in \lbrack 0, 1 \rbrack \)

float

Compute the cosine similarity c for vectors v1 and v2.

jaccard( v1, v2 )

vector

\( J \in \lbrack 0, 1 \rbrack \)

float

Compute the Jaccard index J for vectors v1 and v2.

hamdist( v1, v2 )

vector

\( h \in \lbrack 0, 64 \rbrack \)

int

Compute the hamming distance h (i.e. number of differing bits) between the fingerprints for vectors v1 and v2.

Example: Feature Vector similarity calculation
A = graph.NewVertex( "A" )
B = graph.NewVertex( "B" )
A.SetVector( [ ("hello",1), ("world",1) ] )
B.SetVector( [ ("hello",1), ("there",1) ] )

# -> 0.5
graph.Evaluate( "sim( vertex.vector, next.vector )", tail="A", head="B" )

3.8.7. Similarity (Euclidean Vectors)

Function Argument Type Return Value Description

sim( v1, v2 )
cosine( v1, v2 )

vector

\( s \in \lbrack -1, 1 \rbrack \)

Compute the cosine similarity s for vectors v1 and v2.

hamdist( v1, v2 )

vector

\( h \in \lbrack 0, 64 \rbrack \)

Compute the hamming distance h (i.e. number of differing bits) between the fingerprints for vectors v1 and v2.

Example: Euclidean Vector similarity calculation
A = graph.NewVertex( "A" )
B = graph.NewVertex( "B" )
A.SetVector( [ 0.8, 0.2, -0.9, 0.7 ] + [0]*28 )
B.SetVector( [ 0.3, 0.2, -0.5, 0.5 ] + [0]*28 )

# -> 0.9672
graph.Evaluate( "sim( vertex.vector, next.vector )", tail="A", head="B" )

3.8.8. Control Functions

Function Return Value Description

return( x )

x

Immediately terminate execution of expression and set the return value of the expression to x.

returnif( cond, x )

x if cond is true, otherwise 0

If cond is true, immediately terminate execution of expression and set the return value of the expression to x. If cond is false this function returns 0 and execution continues.

require( cond )

0

If cond is true this function returns 0 and execution continues. If cond is false, immediately terminate execution of expression and set the return value of the expression to 0.

halt()

1 if halted, else 0

Terminate execution of query containing this expression. Evaluation of expression continues until completion, after which all other query activity is terminated immediately without raising an exception. This function returns 1 if termination will occur, or 0 if query cannot be terminated.

haltif( cond )

cond

If cond is true perform halt().

halted()

1 if halted, 0 if not halted

Return the status of query execution termination.

continue()

1

Continue query execution. If query execution was previously halted, termination is cancelled and the query continues executing.

continueif( cond )

cond

If cond is true, continue query execution. If query execution was previously halted, termination is cancelled and the query continues executing.

firstval( x1, x2, …​, xn )

xkmin

Return the leftmost argument xkmin that is a valid numeric value or other object other than null. If none of the arguments are valid values the last argument xn is returned.

lastval( x1, x2, …​, xn )

xkmax

Return the rightmost argument xkmax that is a valid numeric value or other object other than null. If none of the arguments are valid values the first argument x1 is returned.

do( <expr1>, …​, <exprn> )

true

Evaluate any number of sub-expressions <exprk> (discarding their return values) and finally return true. This provides a way to execute expressions that don’t contribute directly to the overall expression result value, but may be needed for their side-effects such as operating on memory locations that will be used later.

void( <expr1>, …​, <exprn> )

null

Evaluate any number of sub-expressions <exprk> (discarding their return values) and finally return null. This provides a way to execute expressions that don’t contribute directly to the overall expression result value, but may be needed for their side-effects such as operating on memory locations that will be used later.

Example: Miscellaneous control function calls
# Use return() for clarity
graph.Evaluate( "a=1+2; return( a )" ) # -> 3

# Stop execution when returnif() condition satisfied
V = graph.NewVertex("V")
V['x'] = 1
V['y'] = 2
graph.Evaluate( "a=next['x']; returnif(a,a); next['y']", head="V" ) # -> 1

# Return first argument
graph.Evaluate( "firstval()" ) # -> nan
graph.Evaluate( "firstval( 1, 2, 3, 4 )" ) # -> 1
graph.Evaluate( "firstval( -1/-0.0, asin(2), 42, 43 )" ) # -> 42
A = graph.NewVertex( "A" )
graph.Evaluate( "firstval( vertex['xxx'], 123 )", tail="A" ) # -> 123

# Return last argument
graph.Evaluate( "lastval( 1, 2, 3, 4 )" )  # -> 4

# Execute anything and return 1
graph.Evaluate( "do()" ) # -> 1
graph.Evaluate( "do( 0, 0/-0.0)" ) # -> 1

# Execute anything and return nan
graph.Evaluate( "void( 1, 2, 3 )" ) # -> nan

3.8.9. Variadic Aggregator Functions

Function Return Value Description Comment

sum( x1, x2, …​, xn )

\( y = \displaystyle \sum_{i=1}^n x_i \)

Sum of all numeric arguments xi

sumsqr( x1, x2, …​, xn )

\( y = \displaystyle \sum_{i=1}^n x_i^2 \)

Sum of the squares of all numeric arguments xi

stdev( x1, x2, …​, xn )

\( y = \displaystyle \sqrt{ \frac{\displaystyle \sum_{i=1}^n {x_i^2} - \frac{\left( \sum_{i=1}^n {x_i} \right) ^ 2}{n}}{n-1} } \)

Standard deviation of numeric arguments xi

invsum( x1, x2, …​, xn )

\( y = \displaystyle \sum_{i=1}^n \frac{1}{x_i} \)

The inverse sum of all numeric arguments xi

(xi)−1 ≈ 8.5·1038 when xi = 0

prod( x1, x2, …​, xn )

\( y = \displaystyle \prod_{i=1}^n x_i \)

Product of all numeric arguments xi

mean( x1, x2, …​, xn )

\( y = \frac{1}{n} \displaystyle \sum_{i=1}^n x_i \)

Arithmetic mean (i.e. average) of all numeric arguments xi

harmmean( x1, x2, …​, xn )

\( y = \left( \frac{1}{n} \displaystyle \sum_{i=1}^n \frac{1}{x_i} \right) ^ {-1} \)

Harmonic mean of all numeric arguments xi

(xi)−1 ≈ 8.5·1038 when xi = 0

geomean( x1, x2, …​, xn )

\( y = \displaystyle \sqrt[n]{ \prod_{i=1}^n x_i } \)

Geometric mean of all numeric arguments xi

These functions also accept numeric array vertex properties as xi. When multiple properties and/or scalar xi are mixed, the result is as if all arrays were flattened prior to the operation.

Example: Aggregator function calls
graph.Evaluate( "sum( 2, 2, 3, 5, 7 )" ) # -> 19
graph.Evaluate( "sumsqr( 2, 2, 3, 5, 7 )" ) # -> 91
graph.Evaluate( "stdev( 2, 2, 3, 5, 7 )" ) # -> 2.17...
graph.Evaluate( "invsum( 2, 2, 3, 5, 7 )" ) # -> 1.68...
graph.Evaluate( "prod( 2, 2, 3, 5, 7 )" ) # -> 420
graph.Evaluate( "mean( 2, 2, 3, 5, 7 )" ) # -> 3.8
graph.Evaluate( "harmmean( 2, 2, 3, 5, 7 )" ) # -> 2.98...
graph.Evaluate( "geomean( 2, 2, 3, 5, 7 )" ) # -> 3.35...

3.8.10. Simple Ranking Functions

Function Return Value Description

rank( x1, x2, …​, xn )

\( y = \text{next.c1} \cdot \displaystyle \sum_{i=1}^n x_i + \text{next.c0} \)

Compute a rank score y for head vertex next using its ranking coefficients c1 and c0 to evaluate y=c1·X+c0, where X is the sum of all arguments xi.

prox( a, b )

\( y = \displaystyle \frac{256}{256 + | a-b | } \)

Compute a proximity score \( y \in \langle 0,1 ] \) for values a and b. The result is equal to 1.0 when a = b, and moves towards 0.0 as \( |a-b| \to \infty\).

geodist( latA, lonA, latB, lonB)

\( y = \begin{cases} 6371001 \cdot 2 \cdot \arctan{ \frac{\sqrt{a}}{\sqrt{1-a}}} & \quad \text{if } a < 1 \\ 6371001 \cdot \pi & \quad \text{if } a = 1 \end{cases} \)

\( a = \frac{1 - \cos( \varphi_B - \varphi_A )}{2} + \cos{\varphi_A} \cdot \cos{\varphi_B} \cdot \frac{1 - \cos( \lambda_B - \lambda_A )}{2} \)

\( \varphi_x = \displaystyle \frac{ \pi \cdot lat_x}{180} \)

\( \lambda_x = \displaystyle \frac{ \pi \cdot lon_x}{180} \)

Compute approximate distance in meters between two geographic locations A = ( latA, lonA ) and B = ( latA, lonA ). This formula simplifies the calculation by assuming the Earth is a sphere, leading to small errors up to 0.5%.
This function expects arguments in degrees.

geoprox( lat1, lon1, lat2, lon2)

\( y = 1 - \displaystyle \frac{ g(lat_A,lon_A,lat_B,lon_B)}{6371001 \cdot \pi} \)

where \( g() \) is the geodist() function.

Compute a geographic proximity score \( y \in \langle 0,1] \) for two locations A = ( lat1, lon1 ) and B = ( lat2, lon2 ). A location will have a proximity score 1.0 to itself and 0.0 to a point on the opposite side of the Earth.
This function expects arguments in degrees.

georank( v )
georank( lat, lon )

\( y = \begin{cases} p(\text{next}_{c1},\text{next}_{c0},v_{c1},v_{c0}) & \quad \text{if one arg} \\ p(\text{next}_{c1},\text{next}_{c0},lat,lon) & \quad \text{if two args} \end{cases}\)

where \( p() \) is the geoprox() function.

Compute a rank score y for head vertex next as a function of its geographic proximity to another vertex v or to a location given by lat and lon. The coordinates of head vertex next are determined by its ranking coefficients where c1=latitude and c0=longitude. When a single v argument is provided it is interpreted as a vertex instance whose latitude and longitude are represented by that instance’s c1 and c0 coefficients. When two arguments lat and lon are provided they are interpreted as the coordinates against which next will be compared. All values of c1, c0, lat, and lon are expressed in degrees.

Example: Miscellaneous ranking functions
graph.Evaluate( "prox( 1000, 1050 )" ) # -> 0.8366...
graph.Evaluate( "geodist( 42.3, 71.0, 35.7, -139.7 )" ) # -> 10797873.8
A = graph.NewVertex( "A" )
A.c0 = 1000
A.c1 = 2.5
graph.Evaluate( "rank()", head="A") # -> 1000.0
graph.Evaluate( "rank(10)", head="A") # -> 1025.0
graph.Evaluate( "rank(10,1,2)", head="A") # -> 1032.5

3.8.11. Collector Staging Functions

Function Return Value Description

stage( [ value[, slot] ] )

\( y = \begin{cases} 1 & \quad \text{if arc staged} \\ 0 & \quad \text{if not staged} \\ -1 & \quad \text{if error} \end{cases} \)

Store the currently traversed arc (i.e. next.arc) into temporary staging buffer slot=0. Optionally override the staged arc’s value with value. Optionally specify staging buffer \( slot \in \{0,1,2,3\} \).

stageif( cond [, value[, slot] ] )

cond

If condition cond is true store the currently traversed arc (i.e. next.arc) into temporary staging buffer slot=0. Optionally override the staged arc’s value with value. Optionally specify staging buffer \( slot \in \{0,1,2,3\} \).

unstage( [ slot ] )

\( y = \begin{cases} 1 & \quad \text{if arc unstaged} \\ 0 & \quad \text{if not unstaged} \\ -1 & \quad \text{if error} \end{cases} \)

Remove any staged arc from selected slot, which defaults to 0 if not specified.

unstageif( cond [, slot ] )

cond

if condition cond is true remove any staged arc from selected slot, which defaults to 0 if not specified.

commit( [ slot ] )

\( y = \begin{cases} 1 & \quad \text{if arc collected} \\ 0 & \quad \text{if not collected} \\ -1 & \quad \text{if error} \end{cases} \)

Collect staged arc in selected slot into result set. Default slot=0.

commitif( cond [, slot ] )

cond

If cond is true collect staged arc in selected slot into result set. Default slot=0.

collect( [ value ] )

\( y = \begin{cases} 1 & \quad \text{if arc collected} \\ 0 & \quad \text{if not collected} \\ -1 & \quad \text{if error} \end{cases} \)

Collect the currently traversed arc (i.e. next.arc) directly into the result set (bypassing any temporary staging.) Optionally override the collected arc’s value with value.

collectif( cond [, value ] )

cond

If condition cond is true collect the currently traversed arc (i.e. next.arc) directly into the result set (bypassing any temporary staging.) Optionally override the collected arc’s value with value.

Example: Collect neighbor’s neighbor arc with the highest value and return the top 10 of those arcs
graph.Neighborhood(
  "A",
  hits     = 10,
  neighbor = {
    'filter'   : "do( store( R1, 0 ) )",
    'adjacent' : {
      'arc'      : ( "to", D_OUT ),
      'filter'   : """
                   void(
                       storeif(
                           stageif( next.arc.value > load( R1 ) ),
                           R1,
                           next.arc.value
                       )
                   )
                   """
    },
    'post'     : "commit()"
  },
  sortby   = S_VAL
)

3.8.12. Breadth-First Search Support

Function Return Value Description

mcull( score, k )

\( y = \begin{cases} 1 & \text{if cand.} \\ 0 & \text{if not cand.} \end{cases} \)

When executed in a 'neighbor': {} vertex condition perform exhaustive evaluation of neighborhood level before any traversal to a deeper neighborhood, constraining the number of vertices that can be anchors for deeper traversal to the at most k vertices with the highest (positive) numeric score.

mcullif( cond, score, k )

cond

Same as mcull() but with the added constraint that cond must be true for the vertex to be a candidate.

Use of this function is restricted to the local filter evaluator in Neighborhood()'s' 'neighbor': {} vertex condition.

The presence of mcull() / mcullif() in a local filter changes the traversal and collection behavior from depth-first to breadth-first. The top-k scoring vertices in the neighborhood are selected before collection of vertices or traversal of vertices' next-level neighborhoods.

A return value 1 from mcull() does not mean the visited vertex will be in the final top k, only that at the time of its evaluation it is among the top k visited up until that point.

Example: Collect arcs from A’s top-5 highest out-degree neighbors
graph.Neighborhood(
  "A",
  collect  = C_NONE,
  neighbor = {
    'filter'   : "mcull( vertex.odeg, 5 )",
    'traverse' : {
      'arc'     : D_OUT,
      'collect' : C_COLLECT
    }
  }
)
Example: Collect arcs from A’s top-5 highest out-degree neighbors, and from those neighbors' neighbors top-3 highest in-degree neighbors
graph.Neighborhood(
  "A",
  collect  = C_NONE,
  neighbor = {
    'filter'   : "mcull(vertex.odeg, 5)",
    'traverse' : {
      'arc'     : D_OUT,
      'collect' : C_COLLECT,
      'neighbor' : {
        'filter'   : "mcull(vertex.ideg, 3)",
        'traverse' : {
            'arc'     : D_OUT,
            'collect' : C_COLLECT
        }
      }
    }
  }
)

4. Evaluator Memory

Memory arrays can be used to implement advanced algorithms. By default a graph query allocates a temporary memory array for use with expressions in the query. This memory is initialized to empty before the query starts executing and is deleted once the query completes.

fig_defaultmem
Figure 1. Default memory array

The default memory array contains eight slots 0 - 7. The last four slots are also accessible as Memory Registers via index aliases R1, R2, R3, and R4. A virtual Memory Stack starts at the index labeled SP and grows downward.

Memory arrays use modulo indexing allowing both positive and negative indices, as well as indices beyond the array end points which are aliased to slots within the array.

Memory array objects can be created with pyvgx.Memory() and passed to graph queries using the memory= query parameter. Queries will then use the supplied memory array instead of the default. The memory array is not initialized by the query and will remain after the query completes. A query may read and/or modify the memory array via one or more expressions in the query. The same memory object can be passed to subsequent queries for further processing.

Memory array objects are accessible from Python as regular list objects.

Memory array objects can have any size, only limited by system resources. Size is always a power of two, 2p where p is the array order.

fig_memarray
Figure 2. Memory array object with 2p slots (order=p)
Example: Create arc value histogram
# Assumption: arc values are 0-99
# Create array with 100 slots (actual size will be 128)
mem = graph.Memory( 100 ) # size rounded up to nearest power of two
graph.Neighborhood(
    id     = "A",
    memory = mem,
    filter = "inc( int(.arc.value) )"
)
mem[0]  # freq of val=0
mem[1]  # freq of val=1
mem[2]  # freq of val=2
#...
mem[99] # freq of val=99

4.1. Memory Registers

The last four slots in a memory object are the registers R1 - R4. There is nothing special about these slots other than the convenience of being able to reference them in expressions using predefined index constants R1, R2, R3, and R4.

fig_memreg
Figure 3. Four registers at end of memory array
Example: Use memory registers
mem = graph.Memory( 16 )
graph.Evaluate( "store(R1, 111)", memory=mem )
graph.Evaluate( "store(R2, 222)", memory=mem )
mem[ R1 ]         # -> 111 (memory object as list)
mem.R1            # -> 111 (memory object attribute)
mem[ -1 ]         # -> 111 (indexing from end)
mem[ 15 ]         # -> 111 (indexing from start)
mem.R1 + mem.R2   # -> 333
graph.Evaluate( "load(R1)*load(R2)", memory=mem ) # -> 24642
To access the contents of a register it must be dereferenced using functions like load(), store(), etc., as shown in the example above. I.e. a constant like R1 is just the number -1 which represents a memory address.

4.2. Memory Stack

A virtual "stack" starts just below the registers and grows downward. A push operation puts a value on the stack and a pop operation removes a value from the stack and returns it. If the stack reaches the bottom of the array (index 0) it wraps around to the top overwriting register R1, then R2, etc. Likewise, repeated pop operations will move the stack pointer upwards wrapping to 0 when the last index is reached. Be careful to allocate a large enough array to accommodate the algorithm using it and to ensure balanced push and pop operations.

fig_memstack
Figure 4. Memory stack
Using push and pop provides no protection against unbalanced calls. Make sure your algorithm is guaranteed to execute an equal number of each operation to avoid leaking the stack into other areas of the memory array you may be using for other purposes.
Example: Using the virtual stack
mem = graph.Memory( 16 )
graph.Evaluate( "push( 111 )", memory=mem )
graph.Evaluate( "push( 222 )", memory=mem )
graph.Evaluate( "push( 333 )", memory=mem )
mem[ 11 ]   # -> 111
mem[ 10 ]   # -> 222
mem[ 9 ]    # -> 333
graph.Evaluate( "pop()", memory=mem )   # -> 333
graph.Evaluate( "pop()", memory=mem )   # -> 222
graph.Evaluate( "pop()", memory=mem )   # -> 111

4.3. Memory Indexing

Memory arrays are modulo indexed, protecting system memory from invalid access and allowing negative indices to reference the array relative to its end. Although primarily included as a safeguard against invalid memory access you may find modulo indexing to be a useful feature when implementing certain algorithms.

fig_moduloindexing
Figure 5. Memory array slots have multiple aliases due to modulo indexing
Example: Modulo indexing
mem = graph.Memory( 16 )
graph.Evaluate( "store( 3, 555 )", memory=mem )
graph.Evaluate( "load( 3 )", memory=mem )    # ->   555
graph.Evaluate( "load( 19 )", memory=mem )    # ->   555
graph.Evaluate( "load( -13 )", memory=mem )     # ->   555
To restrict the indexing span to a sub-range of the array use the modindex() function to compute the array index.

4.4. Memory Python Interface for Assignment and Lookup

The Python interface for accessing pyvgx.Memory elements is similar to list. Lookup and assignment use the standard array subscript syntax [n] and slice syntax [a:b].

4.4.1. Memory Subscript

For subscript syntax [n] the behavior is the same as for list.

Table 4. Memory subscript and value types (assignment)
Syntax Python Value Type Stored Value Type Description

memobj[ n ] = value

int

integer

Assign an integer value to memory location n

memobj[ n ] = value

float

real

Assign a real value to memory location n

memobj[ n ] = value

str

string

Assign a string value to memory location n

memobj[ n ] = value

pyvgx.Vector

vector

Assign a vector object to memory location n

memobj[ n ] = [ value ]

[ int ]

bitvector

Assign integer value as a 64-bit bitvector to memory location n

memobj[ n ] = ( key, value )

( int, float )

keyval

Assign a keyval tuple to memory location n

memobj[ n ] = bytearray( list )

bytearray

raw bytes

Assign an array of raw (unsigned) bytes to memory location n

memobj[ n ] = bytes( data )

bytes

raw bytes (immutable)

Assign an immutable array of (unsigned) bytes to memory location n

Table 5. Memory subscript and value types (lookup)
Syntax When Stored Value Type Is…​ Python Value Type Description

memobj[ n ]

integer

int

Retrieve an integer value from memory location n as a Python int

memobj[ n ]

real

float

Retrieve a real value from memory location n as a Python float

memobj[ n ]

string

str

Retrieve a string value from memory location n as a Python str

memobj[ n ]

vector

pyvgx.Vector

Retrieve a vector object from memory location n as pyvgx.Vector

memobj[ n ]

bitvector

[ int ]

Retrieve a bitvector from memory location n as a Python list with a single int value

memobj[ n ]

keyval

( int, float )

Retrieve a keyval from memory location n as a Python tuple with a 32-bit integer key and a single precision float value

memobj[ n ]

raw bytes

bytearray

Retrieve a sequence of raw bytes from memory location n as a Python bytearray object

memobj[ n ]

raw bytes

bytes

Retrieve an array of bytes from memory location n as a Python bytes object

Memory indexing
mem = graph.Memory(32)
mem[10] = 1234      # Assign 1234 to memory location 10
mem[11] = 3.14      # Assign 3.14 to memory location 11
mem[12] = "hello"   # Assign string "hello" to memory location 13
mem[13] = [0xFF00]  # Assign bitvector 0xFF00 to memory location 12
mem[14] = (42, 1.7) # Assign keyval 42:1.7 to memory location 14
mem[15] = bytearray( [10,20,0,0,50] ) # Assign raw bytes to mem 15
mem[16] = bytes( [1,2,3,4] ) # Assign raw bytes to mem 16
mem[17] = graph.sim.rvec(64) # Assign random vector to mem 17

# int
list( mem[10] )       # <class 'int'>
print( mem[10] )      # 1234
print( mem[-22] )     # 1234 (modulo indexing)

# float
type( mem[11] )       # <class 'float'>
print( mem[11] )      # 3.14

# str
type( mem[12] )       # <class 'str'>
print( mem[12] )      # hello

# bitvector
type( mem[13] )       # <class 'list'>
print( mem[13] )      # [65280]

# key/val
type( mem[14] )       # <class 'tuple'>
print( mem[14] )      # (42, 1.7000000476837158)

# bytearray
type( mem[15] )       # <class 'bytearray'>
list( mem[15] )       # [10, 20, 0, 0, 50]

# bytes
type( mem[16] )       # <class 'bytes'>
list( mem[16] )       # [1,2,3,4]

# vector
type( mem[17] )       # <class 'pyvgx.Vector'>
print( mem[17] )      # <PyVGX_Vector: ...>

4.4.2. Memory Slice

For slice syntax [a:b] the behavior is the same as for list with the exception of assignment which differs when the assignment list is smaller than the slice. The memory object is updated by applying the new values starting from the beginning of the slice and leaving the end of the slice unmodified. The behavior of list is to replace the entire slice with the new assigned values.

Just as for list slices may omit either end point, and negative indices reference backwards from the end.

Table 6. Memory slice and value types
Syntax Value Types Description

memobj[ a:b ] = [ v1,v2,…​,vn ]

intinteger
floatreal
strstring
[int] → bitvector
(int,float) → keyval
bytearray → raw bytes
bytes → raw bytes

Assign values v1 through vn to memory locations starting at a. If n < b - a the end of the memory slice remains unaffected. (This behavior is different from Python list.)

memobj[ a:b ]

intinteger
floatreal
strstring
[int] ← bitvector
(int,float) ← keyval
bytearray ← raw bytes
bytes ← raw bytes

Return a list of values from memory locations a through b - 1.

Memory slices
mem = graph.Memory(8)
mem[4:8] = [44,55,66,7.89]  # Assign multiple values to slice
mem[4:8] = [444,555]        # Partial slice assignment
print( mem[4:8] )           # [444, 555, 66, 7.89]

print( mem[:] )             # [0, 0, 0, 0, 444, 555, 66, 7.89]
print( mem[:5] )            # [0, 0, 0, 0, 444]
print( mem[3:] )            # [0, 444, 555, 66, 7.89]
print( mem[:-2] )           # [0, 0, 0, 0, 444, 555]

4.4.3. Memory Initialization

New memory objects can be initialized to specific values by passing an initializer list to the constructor. An existing memory object can have all its elements reset to a certain constant value or range of values.

Memory object initialization and reset
# Construct using initializer
mem = graph.Memory( [100,1,1,1,50] )
# [100, 1, 1, 1, 50, 0, 0, 0]
len( mem ) # 8, smallest power of two to hold initializer

# Reset all elements to zero
mem.Reset()
# [0, 0, 0, 0, 0, 0, 0, 0]

# Reset all elements to 2.5
mem.Reset( 2.5 )
# [2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5]

# Reset elements to a range
mem.Reset( 1, 5 ) # First value is 1, step is 5
# [1, 6, 11, 16, 21, 26, 31, 36]

4.4.4. Memory Sort

Memory objects can be sorted. It is possible to sort the entire array or a specific range within the array. The element comparator used for sorting is automatically chosen by the type of the first element in the range to be sorted.

Memory sorting
mem = graph.Memory( 32 )

# Randomize 16 elements 0 through 15
graph.Evaluate( "mrandomize(0,15)", memory=m )

# Sort 16-element range 0 through 15
mem.Sort( 0, 16 )

# Reverse sort
mem.Sort( 0, 16, reverse=True )

# Sort 28 non-register elements
mem.Sort()

4.4.5. A Note on Memory Value Types

Although memory objects internally can hold elements of any type supported by the evaluator engine, the only element types accessible by the Python interpreter are integer, real, string, bitvector, keyval, bytearray and bytes which map to Python types or objects int, float, str, [int], (int,float), bytearray and bytes, respectively. Element types null, nan and inf in memory objects are accessed by the Python interpreter as Python type float with values nan, nan and inf, respectively.

integer

Maps directly to Python int

mem[0] = 123
graph.Evaluate( "isint(load(0))", memory=mem ) # -> true
mem[0] # -> 123
real

Maps directly to Python float

mem[1] = 3.14
graph.Evaluate( "isreal(load(1))", memory=mem ) # -> true
mem[1] # -> 3.14
string

This type is interpreted differently in different contexts. When accessing a Python memory object the evaluator string type maps directly to Python str with implicit UTF-8 encoding internally. When the evaluator engine executes an expression the string type also encompasses the bytearray and bytes types, i.e. the execution engine treats bytearray and bytes as subtypes of string.

mem[2] = 'hello'
g.Evaluate( "isstr(load(2))", memory=mem ) # -> true
mem[2] # -> 'hello'
bitvector

This is a pseudo-type represented by a single-element Python list where the element is a Python int. Bitvectors are assigned as mem[n] = [bv]. When retrieving the bitvector value it is returned as a singleton list [bv]. Evaluator expressions can also assign bitvectors, e.g. "store( 0, bitvector( 0xFF00 ) )".

mem[3] = [0xfff]
graph.Evaluate( "isbitvector(load(3))", memory=mem ) # -> true
mem[3] # -> [4095]
keyval

This is a pseudo-type represented by a two-element Python tuple where the first element is a Python int and the second element is a Python float. Keyval items are assigned as mem[n] = (key, value). Note that key has a 32-bit range and value is truncated to single precision internally.

mem[4] = (123,3.14)
graph.Evaluate( "iskeyval(load(4))", memory=mem ) # -> true
mem[4] # -> 123, 3.140000104904175)
bytearray and bytes

These types are applicable only when interacting with memory objects from Python. The Python bytearray and bytes types are internally represented as subtypes of string, which means that evaluator expressions operate on all string data as arrays of bytes. When assigning to a memory location as mem[n] = bytearray( …​ ) or mem[n] = bytes( …​ ) the internal object is string with attributes indicating the subtype.

mem[5] = bytearray([1,2,3])
graph.Evaluate( "isbytearray(load(5))", memory=mem ) # -> true
graph.Evaluate( "isstr(load(5))", memory=mem ) # -> true
mem[5] # -> bytearray(b'\x01\x02\x03')

mem[6] = bytes([1,2,3])
graph.Evaluate( "isbytes(load(6))", memory=mem ) # -> true
graph.Evaluate( "isstr(load(6))", memory=mem ) # -> true
mem[6] # -> b'\x01\x02\x03'
null, nan, inf

These types indicating "no value" in various contexts are all interpreted as Python float when retrieved from memory objects. They cannot be set from Python.

graph.Evaluate( "store(7,null)", memory=mem ) # -> None
type(mem[7]) # -> <class 'float'>
mem[7] # -> nan

graph.Evaluate( "store(8,nan)", memory=mem ) # -> nan
type(mem[8]) # -> <class 'float'>
mem[8] # -> nan

graph.Evaluate( "store(9,inf)", memory=mem ) # -> inf
type(mem[9]) # -> <class 'float'>
mem[9] # -> inf
vector

Maps directly to Python pyvgx.Vector

mem[10] = g.sim.rvec(128)
g.Evaluate( "isvector(load(10))", memory=mem ) # -> true
mem[10] # -> '<PyVGX_Vector: typ=euclidean len=128 mag=6.948...'

4.4.6. A note about strings and vectors in evaluator expressions

String and vector objects can be assigned to memory locations from within expressions and from the Python interpreter. The pyvgx.Memory instance becomes an owner of the assigned object, either by creating a copy or by owning an additional reference.

String and vector objects are retained by the pyvgx.Memory associated with the expression being evaluated. Each time a string-generating function (such as str() or replace()) is used, or a string or vector object is assigned to a memory location, the pyvgx.Memory instance will accumulate ownership of assigned objects even if the memory location is later overwritten by another value.

If a memory instance is long-lived manual cleanup of strings and vectors is required to avoid unbounded buildup of objects that are no longer in use. Cleanup is performed explicitly via pyvgx.Memory.Reset() or evaluator function "mreset()". (Cleanup is automatic when the pyvgx.Memory instance itself is deleted.)

Note that copying existing strings or vectors between memory locations does not create new instances or additional ownership when performed by an expression. For instance, "store( 0, str('hello') ); mov( 1, 0 )" creates one new string object referenced from two separate memory locations, i.e. locations 0 and 1 point to the same string object owned once by the memory object. (Python interpreter copy (e.g. mem[1] = mem[0]) is normal assignment and therefore creates new object ownership each time.)

Example: Value Types
mem = graph.Memory( 16 )

mem[0] = 0xFF0000     # Type is integer
mem[1] = [ 0xFF0000 ] # Type is bitvector
mem[2] = 3.14         # Type is real
mem[3] = "Alice"      # Type is string
mem.R1 = graph.sim.rvec(64) # Type is vector
mem.R2 = graph.sim.rvec(64) # Type is vector

mem[0]                # -> 16711680
mem[1]                # -> [16711680]
mem[2]                # -> 3.14
mem[3]                # -> 'Alice'
mem.R1                # -> <PyVGX_Vector: ...>
mem.R2                # -> <PyVGX_Vector: ...>

graph.Evaluate( "isbitvector( load(0) )", memory=mem ) # -> 0
graph.Evaluate( "isbitvector( load(1) )", memory=mem ) # -> 1
graph.Evaluate( "mul( 2, 10 )", memory=mem )           # -> 31.4
graph.Evaluate( "load( 3 ) == 'Ali*'  ", memory=mem )  # -> 1
graph.Evaluate( "store( 4, 'Bob' )", memory=mem )      # -> 'Bob'
graph.Evaluate( "mov( 5, 3 )", memory=mem )            # -> 'Alice'
graph.Evaluate( "mov( 6, 4 )", memory=mem )            # -> 'Bob'
graph.Evaluate( "isvector(r1)", memory=mem )           # -> 1
graph.Evaluate( "cosine(r1,r2)", memory=mem )          # -> 0.02157

# Strings 'Alice' and 'Bob', and vectors are now deleted
graph.Evaluate( "mreset()", memory=mem )

5. Memory Operation Reference

5.1. Notation Conventions

The following notation is used to describe operations listed in all tables below.

This notation is for describing how functions work, and has no relation to expression syntax.
Simple Assignment :=

The simple assignment operator := assigns the right operand to the left operand. For example, to indicate assignment of value x to memory location a we write [ a ] := x.

Operator Assignment <op>=

When the value of a left operand is modified by the right operand the general <op>= syntax is used, such as +=, /=, >>=, etc. For example [ addr ] /= x means the value at location addr is divided by x and the result written back to location addr.

Exchange :=:

The exchange operator :=: swaps the two operands. For example, [ a ] :=: [ b ] means the values at memory locations a and b are swapped.

Dereference []

Dereference notation [ addr ] is used to represent the contents at memory address addr.

Double Dereference [[]]

Double dereference notation [[ addr ]] is used to represent the contents at address stored at address addr. For example, [[ a ]] := x means the value x is written to memory location stored in location a. As a concrete example, if a = 1000 and the value 50 is stored in location 1000 then [[ a ]] = 7 will assign 7 to location 50.

Range Dereference [:]

Dereference notation [ a : b ] is used to indicate the contents of a range of addresses starting at address a and ending at address b, both endpoints included. For example, to assign value x to all memory locations starting at a and ending at b we write [ a : b ] := x When the right operand is or includes a range the right element i is assigned to left element i. For example, [ a : b ] := operation( [ a : b ] ) means the operation is applied to each element starting at location a and ending at location b.

Operations taking a memory range input are inclusive of the end index. (This is different from how ranges are processed by the Python interpreter, where the end index is not included.) Negative indices are allowed. They count backwards from the element just beyond the end of the memory array, i.e. index -1 references the very last element.

5.2. Operations on Single Memory Locations

Table 7. Quick Links
Category Functions

Storing and Loading

store(), write(), load(), etc.

Stack

push(), pop(), etc.

Moving Data

mov(), xchg(), etc.

Increment and Decrement

inc(), dec(), etc.

Comparator

equ(), neq(), gt(), lte(), etc.

Arithmetic

add(), sub(), mul(), div(), etc.

Bitwise

shr(), shl(), and(), or(), xor(), etc.

Vector Arithmetic

dp_pi8(), cos_pi8(), etc.

Various Functions

smooth(), count(), etc.

Indexing

modindex(), index(), etc.

5.2.1. Storing and Loading

Table 8. Storing and Loading
Function Operation Ret Description

Store

store( dest, x )

[dest] := x

[dest]

Assign numeric value x to memory location dest and return x

rstore( pdest, x )

[[ pdest ]] := x

[[ pdest ]]

Assign numeric value x to memory location pointed to by address in pdest and return x

storeif( cond, dest, x )

[dest] := x if cond == true

cond

If cond is nonzero assign numeric value x to memory location dest. Always returns cond.

rstoreif( cond, pdest, x )

[[ pdest ]] := x if cond == true

cond

If cond is nonzero assign numeric value x to memory location pointed to by address in pdest. Always returns cond.

Write

write( dest1, x1, x2, …​, xn )

[dest1 : destn] := x1, x2, …​, xn

n

Assign n numeric values x1 through xn to a range of consecutive destinations starting at dest1. The number n of values to write equals the number of value arguments xi and therefore destn is implied. If the end of the memory array is reached during writing, the next destination will be index 0 and writing continues until all xi have been written.

writeif( cond, dest1, x1, …​, xn )

[dest1 : destn] := x1, …​, xn if cond == true

cond

If cond is nonzero the remaining arguments are passed to write(). Always returns cond.

rwrite( pdest, x1, x2, …​, xn )

[[pdest]++] := x1
[[pdest]++] := x2
…​
[[pdest]++] := xn

n

Assign n numeric values x1 through xn to a range of consecutive destinations starting at the index referenced by pdest, incrementing the index in pdest after each iteration. If the end of the memory array is reached during writing, modulo indexing ensures the next destination will be index 0 and writing continues until all xi have been written. The value in pdest will have been incremented by n after the operation completes.

rwriteif( cond, pdest, x1, …​, xn )

If cond is true then:
[[pdest]++] := x1
[[pdest]++] := x2
…​
[[pdest]++] := xn

cond

If cond is nonzero the remaining arguments are passed to rwrite(). Always returns cond.

Load

load( src )

[src]

[src]

Return value at memory location src

rload( psrc )

[[ psrc ]]

[[ psrc ]]

Return value at memory location pointed to by psrc

5.2.2. Stack

Table 9. Stack
Function Operation Ret Description

push( x )

[stackptr] := x, stackptr −= 1

x

Push numeric value x onto stack and return x

pushif( cond, x )

if cond == true then
[stackptr] := x, stackptr −= 1

cond

If cond is nonzero push numeric value x onto stack. Always returns cond.

pop()

stackptr += 1, x := [stackptr]

x

Pop value from stack and return it

get()

x := [stackptr]

x

Return value at top of stack without popping

5.2.3. Moving Data

Table 10. Moving Data
Function Operation Ret Description

Copy

mov( dest, src )

[dest] := [src]

[dest]

Copy the value at memory location src into memory location dest and return the copied value

rmov( pdest, psrc )

[[ pdest ]] := [[ psrc ]]

[[ pdest ]]

Copy the value at memory location pointed to by address in psrc into memory location pointed to by address in pdest and return the copied value

movif( cond, dest, src )

[dest] := [src]
if cond == true

cond

If cond is nonzero copy the value at memory location src into memory location dest. Always returns cond.

rmovif( cond, pdest, psrc )

[[ pdest ]] :=[[ psrc ]]
if cond == true

cond

If cond is nonzero copy the value at memory location pointed to by address in psrc into memory location pointed to by address in pdest. Always returns cond.

Exchange

xchg( m1, m2 )

[m1] :=: [m2]

[m1]

Exchange the values at memory locations m1 and m2 and return the value at location m1 after the exchange (i.e. the value originally in m2)

rxchg( pm1, pm2 )

[[ pm1 ]] :=: [[ pm2 ]]

[[ pm1 ]]

Exchange the values at memory locations pointed to by addresses in pm1 and pm2 and return the value at location pointed to by address in pm1 after the exchange (i.e. the value originally pointed to by address in pm2)

xchgif( cond, m1, m2 )

[m1] :=: [m2]
if cond == true

cond

If cond is nonzero exchange the values at memory locations m1 and m2. Always returns cond.

rxchgif( cond, pm1, pm2 )

[[ pm1 ]] :=:[[ pm2 ]]
if cond == true

cond

If cond is nonzero exchange the values at memory locations pointed to by addresses in pm1 and pm2. Always returns cond.

5.2.4. Increment and Decrement

Table 11. Increment and Decrement
Function Operation Ret Description

Increment

inc( dest )

[dest] += 1

[dest]

Increment value at memory location dest and return the new value

rinc( pdest )

[[ pdest ]] += 1

[[ pdest ]]

Increment value at memory location pointed to by address in pdest and return the new value

incif( cond, dest )

[dest] += 1 if cond == true

cond

If cond is nonzero increment value at memory location dest. Always returns cond.

rincif( cond, pdest )

[[ pdest ]] += 1 if cond == true

cond

If cond is nonzero increment value at memory location pointed to by address in pdest. Always returns cond.

Decrement

dec( dest )

[dest] −= 1

[dest]

Decrement value at memory location dest and return the new value

rdec( pdest )

[[ pdest ]] −= 1

[[ pdest ]]

Decrement value at memory location pointed to by address in pdest and return the new value

decif( cond, dest )

[dest] −= 1 if cond == true

cond

if cond is nonzero decrement value at memory location dest. Always returns cond.

rdecif( cond, pdest )

[[ pdest ]] −= 1 if cond == true

cond

if cond is nonzero decrement value at memory location pointed to by address in pdest. Always returns cond.

5.2.5. Comparator

Table 12. Comparator
Function Operation Ret Description

Equality

equ( a, b )

c := [a] == [b]

c

Return true if items in memory locations a and b compare equal, else return false

requ( pa, pb )

c := [[ pa ]] == [[ pb ]]

c

Return true if items pointed to by addresses in memory locations pa and pb compare equal, else return false

neq( a, b )

c := [a] != [b]

c

Return true if items in memory locations a and b compare not equal, else return false

rneq( pa, pb )

c := [[ pa ]] != [[ pb ]]

c

Return true if items pointed to by addresses in memory locations pa and pb compare not equal, else return false

Greater

gt( a, b )

c := [a] > [b]

c

Return true if item in memory location a compares greater than item in memory location b, else return false

rgt( pa, pb )

c := [[ pa ]] > [[ pb ]]

c

Return true if item pointed to by address in memory location pa compares greater than item pointed to by address in memory location pb, else return false

gte( a, b )

c := [a] >= [b]

c

Return true if item in memory location a compares greater than or equal to item in memory location b, else return false

rgte( pa, pb )

c := [[ pa ]] >= [[ pb ]]

c

Return true if item pointed to by address in memory location pa compares greater than or equal to item pointed to by address in memory location pb, else return false

Less

lt( a, b )

c := [a] < [b]

c

Return true if item in memory location a compares less than item in memory location b, else return false

rlt( pa, pb )

c := [[ pa ]] < [[ pb ]]

c

Return true if item pointed to by address in memory location pa compares less than item pointed to by address in memory location pb, else return false

lte( a, b )

c := [a] <= [b]

c

Return true if item in memory location a compares less than or equal to item in memory location b, else return false

rlte( pa, pb )

c := [[ pa ]] <= [[ pb ]]

c

Return true if item pointed to by address in memory location pa compares less than or equal to item pointed to by address in memory location pb, else return false

5.2.6. Arithmetic

Table 13. Arithmetic
Function Operation Ret Description

Addition

add( addr, x )

[addr] += x

[addr]

Add x to value at memory location addr and return the sum

addif( cond, addr, x )

[addr] += x if cond == true

cond

If cond is nonzero add x to value at memory location addr. Always returns cond.

Subtraction

sub( addr, x )

[addr] −= x

[addr]

Subtract x from value at memory location addr and return the difference

subif( cond, addr, x )

[addr] −= x if cond == true

cond

If cond is nonzero subtract x from value at memory location addr. Always returns cond.

Multiplication

mul( addr, x )

[addr] *= x

[addr]

Multiply value at memory location addr by x and return the product

mulif( cond, addr, x )

[addr] *= x if cond == true

cond

If cond is nonzero multiply value at memory location addr by x. Always returns cond.

Division

div( addr, x )

[addr] /= x

[addr]

Divide value at memory location addr by x and return the quotient

divif( cond, addr, x )

[addr] /= x if cond == true

cond

If cond is nonzero divide value at memory location addr by x. Always returns cond.

Modulo

mod( addr, x )

[addr] %= x

[addr]

Perform modulo by x operation on value at memory location addr and return the remainder

modif( cond, addr, x )

[addr] %= x if cond == true

cond

If cond is nonzero perform modulo by x operation on value at memory location addr. Always returns cond.

5.2.7. Bitwise

Table 14. Bitwise Operations
Function Operation Ret Description

Shift

shr( addr, b )

[addr] >>= b

[addr]

Shift value (interpreted as integer) at memory location addr right by b bits and return the new value.

shrif( cond, addr, b )

[addr] >>= b if cond == true

cond

If cond is nonzero shift value (interpreted as integer) at memory location addr right by b bits. Always returns cond.

shl( addr, b )

[addr] <<= b

[addr]

Shift value (interpreted as integer) at memory location addr left by b bits and return the new value.

shlif( cond, addr, b )

[addr] <<= b if cond == true

cond

If cond is nonzero shift value (interpreted as integer) at memory location addr left by b bits. Always returns cond.

Bitwise AND

and( addr, x )

[addr] &= x

[addr]

Perform bitwise-AND operation on value (interpreted as integer) at memory location addr using bit mask x and return the new value.

andif( cond, addr, x )

[addr] &= x if cond == true

cond

If cond is nonzero perform bitwise-AND operation on value (interpreted as integer) at memory location addr using bit mask x. Always returns cond.

Bitwise OR

or( addr, x )

[addr] |= x

[addr]

Perform bitwise-OR operation on value (interpreted as integer) at memory location addr using bit mask x and return the new value.

orif( cond, addr, x )

[addr] |= x if cond == true

cond

If cond is nonzero perform bitwise-OR operation on value (interpreted as integer) at memory location addr using bit mask x. Always returns cond.

Bitwise XOR

xor( addr, x )

[addr] ^= x

[addr]

Perform bitwise-XOR operation on value (interpreted as integer) at memory location addr using bit mask x and return the new value.

xorif( cond, addr, x )

[addr] ^= x if cond == true

cond

If cond is nonzero perform bitwise-XOR operation on value (interpreted as integer) at memory location addr using bit mask x. Always returns cond.

5.2.8. Vector Arithmetic

Table 15. Vector Arithmetic
Function Operation Ret Description

dp_pi8( A, B )
dp_pi8_256( A, B )
dp_pi8_512( A, B )

\( y := \mathbf{A} \cdot \mathbf{B} \)

where
\( \mathbf{A} = (a_1,a_2,...,a_n) \)
\( \mathbf{B} = (b_1,b_2,...,b_n) \)

i.e.
\( y := a_1b_1 + a_2b_2 + ... + a_nb_n\)

\( a_i \in [-128,127] \)
\( b_i \in [-128,127] \)

Vector length n such that
\( n \mod w = 0 \)

where
\( w = \begin{cases} 32 & \quad \text{for 256 version} \\ 64 & \quad \text{for 512 version} \end{cases}\)

float( y )

Compute the dot-product of vectors A and B represented by bytearrays. Vector lengths must be equal, and a multiple of w bytes where w depends on the function version. Vector elements are interpreted as signed bytes.

The returned value is floating point.

NOTE1: Availability of these functions depends on the pyvgx library build version and the instruction set extensions present on the CPU. The 512 version requires the AVX-512 enabled build and a CPU with AVX-512 support. The 256 version requires the AVX2 enabled build and a CPU with AVX2 support. dp_pi8() is an alias for the highest width version available on the system.

NOTE2: bytearray objects store unsigned byte values in range [0,255]. However, dp_pi8() uses signed arithmetic where values are two’s complement encoded. Raw array values in range [128,255] are therefore interpreted as negative.

NOTE3: A and/or B may be supplied as vector objects (e.g. next.vector). A vector object is implicitly cast to bytearray for the computation. The vector’s scaling factor is applied

cos_pi8( A, B )
cos_pi8_256( A, B )
cos_pi8_512( A, B )

\( y := \cos{ \theta } = \displaystyle \frac{ \mathbf{A} \cdot \mathbf{B}}{ \|\mathbf{A}\| \cdot \|\mathbf{B}\|} \)

where
\( \mathbf{A} = (a_1,a_2,...,a_n) \)
\( \mathbf{B} = (b_1,b_2,...,b_n) \)

i.e.
\( y := \displaystyle \frac{\displaystyle \sum_{i=1}^n {a_ib_i}}{ \sqrt{\displaystyle \sum_{i=1}^n {a_i^2}} \cdot \sqrt{\displaystyle \sum_{i=1}^n {b_i^2} }} \)

\( a_i \in [-128,127] \)
\( b_i \in [-128,127] \)

Vector length n such that
\( n \mod w = 0 \)

where
\( w = \begin{cases} 32 & \quad \text{for 256 version} \\ 64 & \quad \text{for 512 version} \end{cases}\)

y

Compute cosine of angle \( \theta \) between vectors A and B (represented by bytearrays) whose lengths must be equal, and a multiple of w bytes where w depends on the function version. Vector elements are interpreted as signed bytes.

Return float value \( y \in [-1, 1]\)

NOTE1: See dp_pi8() NOTE1 for availability of 512, 256 versions.

NOTE2: See dp_pi8() NOTE2 regarding encoding of signed values.

ecld_pi8( A, B )
ecld_pi8_256( A, B )
ecld_pi8_512( A, B )

\( y := d( \mathbf{A}, \mathbf{B} ) = \displaystyle \|\overrightarrow{ \mathbf{AB} }\| \)

where
\( \mathbf{A} = (a_1,a_2,...,a_n) \)
\( \mathbf{B} = (b_1,b_2,...,b_n) \)

i.e.
\( y := \displaystyle \sqrt{ \sum_{i=1}^n {(a_i - b_i)^2}} \)

\( a_i \in [-128,127] \)
\( b_i \in [-128,127] \)

Vector length n such that
\( n \mod w = 0 \)

where
\( w = \begin{cases} 32 & \quad \text{for 256 version} \\ 64 & \quad \text{for 512 version} \end{cases}\)

y

Compute the Euclidean (L2) norm of the difference between vectors A and B represented by bytearrays. The number of vector dimensions must be a multiple of w bytes where w depends on the function version. Vector elements are interpreted as signed bytes.

Return float value \( y \geq 0 \)

NOTE1: See dp_pi8() NOTE1 for availability of 512, 256 versions.

NOTE2: See dp_pi8() NOTE2 regarding encoding of signed values.

ham_pi8( A, B )

\( y := hamdist( \mathbf{A}, \mathbf{B} ) \)

where
\( \mathbf{A} = (a_1,a_2,...,a_n) \)
\( \mathbf{B} = (b_1,b_2,...,b_n) \)

y

Compute the Hamming Distance between the 64-bit fingerprints (LSH) of vectors A and B (represented by bytearrays or vector objects.) Vector elements are interpreted as signed bytes.

Return integer value \( y \in \lbrack 0, 64 \rbrack \)

NOTE2: See dp_pi8() NOTE2 regarding encoding of signed values.

The 512 versions of the above functions are generally 50% faster than 256 versions for large vectors.

However, there is a cost to using the 512 versions in that the CPU has to switch back and forth between using the wider 512-bit registers and the narrower registers used by other parts of the program code. Furthermore the CPU may automatically throttle down its frequency while executing AVX-512 instructions to remain within thermal bounds, and this could have an impact on both the performance of the vector computation and other concurrently running parts of the system.

Benchmarking is the only way to select the most performant vector function in a given application.

5.2.9. Various Functions

Table 16. Various Functions
Function Operation Ret Description

smooth( addr, x, α )

[addr] += α · ( x − [addr] )

[addr]

Update value at memory location addr from new value x using exponential smoothing factor α and return the updated value.

count( addr [, <expr1>, …​, <exprn>] )

[addr] += 1

[addr]

Evaluate any number of sub-expressions <exprk> (discarding their return values), increment value at memory location addr and return the new value. This provides a way to count the number of times an expression is evaluated and to execute expressions that don’t contribute directly to the overall expression result value, but may be needed for their side-effects such as operating on memory locations that will be used later.

countif( cond, addr[, <expr1>, …​, <exprn>] )

[addr] += 1 if cond == true

[cond]

Same behavior as count() except value at location addr is incremented only when cond is true, and cond is always returned.

5.2.10. Indexing

Table 17. Indexing
Function Operation Ret Description

modindex( i, mod, ofs )

x = i % mod + ofs

x

Compute the array index x for a sub-array index i defined by mod and ofs.

index( vertex )

[vertex.bitindex] |= vertex.bitvector

1 or 0

Add vertex to memory array bitvector index by setting the appropriate bit of the value in a memory location determined by the vertex address

indexed( vertex )

[vertex.bitindex] & vertex.bitvector

1 or 0

Test if vertex has been indexed in the memory array

unindex( vertex )

[vertex.bitindex] &= ~vertex.bitvector

0

Remove vertex from memory array bitvector index by clearing the appropriate bit of the value in a memory location determined by the vertex address

5.3. Operations on Multiple Memory Locations

These operations affect ranges of memory locations.

5.3.1. Function Naming Conventions

Prefix m*

All functions of this type start with the letter m.

Prefix mi*

Some functions are optimized for use with integer arrays and have the mi prefix. These functions have undefined behavior on non-integer data.

Prefix mr*

Some functions are optimized for use with floating point arrays and have the mr prefix. These functions have undefined behavior on non-floating point data.

Prefix mv*

Some functions operate element-wise on two equally sized vectors and have the mv prefix.

5.3.2. Array Indexing and Range Specification

Array index subscript convention

Array indices are specified using 1-based offsets. The array A with k elements has its first element at A1 and its last element at Ak.

Start and end indices

Functions that specify a memory range using start and end indices are inclusive of the end index. In general, a function m<func>( m1, mn[, …​ ] ) applies to memory locations m1, m2, …​, mn.

Array location and length

Functions that specify a memory range using an array location A and its length k generally have the form m<func>( A, k[, …​ ] ) and apply to memory locations A1, A2, …​, Ak.

5.3.3. Applying Operations to Memory Range

The simplified notation \( M_n \) is sometimes used to represent the set of n values in consecutive memory range m1 through mn.

\( M_n ::=\ [ m_1 : m_n ] \),

where dereference notation \( [m] \) is used to represent the value in memory location m.

Using this definition of \( M_n \) we can then describe functions more compactly.

Table 18. Simplified memory range notation examples
Function Example Operation Description

m<f>( m1, mn )

\( M_n := f( M_n ) \)

is the equivalent of

\( [ m_1 : m_n ] := f( [ m_1 : m_n ] ) \)

is the equivalent of

\( [m_i] := f([m_i]) \text{ for all } i \in \{1,2,...,n\} \)

Let \( y = f(x) \) and let \( [ m ] \) represent the value in memory location m. This evaluator function then computes \( y_i = f(x_i) \text{ for all } x_i = [m_i]\),
\( i \in \{m_1, m_2, ..., m_n\} \)
and then performs \( [m_i] = y_i \).

Stated more simply, transform all values in the specified memory range using \(f(x)\).

m<g>( m1, mn, p )

\( M_n := M_n \text{ <g> } p \)

is the equivalent of

\( M_n := g( M_n, p ) \)

is the equivalent of

\( [m_i] := g([m_i],p) \text{ for all } i \in \{1,2,...,n\} \)
where \( g(x,p) = x \text{ <g> } p \)

Let \( y = g(x,p) \) and let \( [ m ] \) represent the value in memory location m. This evaluator function then computes \( y_i = g(x_i,p) \text{ for all } x_i = [m_i]\),
\( i \in \{m_1, m_2, ..., m_n\} \)
and then performs \( [m_i] = y_i \).

Stated more simply, transform all values in the specified memory range using \(g(x,p)\).

Category Functions

Set and Copy

mset(), mreset(), mrandomize(), mcopy(), etc.

Heap Operations

mheapinit(), mheapifymin(), mheappushmin(), etc.

Sorting

msort(), mreverse(), etc.

Type Cast

mint(), mreal(), etc.

Increment and Decrement

minc(), mdec(), etc.

Arithmetic

madd(), mvadd(), mvmul(), mpow(), msqrt(), etc.

Numeric

mceil(), mround(), mabs(), etc.

Logarithms and Exponential Functions

mlog(), mlog2(), mexp(), mexp10(), etc.

Trigonometric and Hyperbolic

msin(), macos(), mtanh(), msinc(), etc.

Bitwise Operations

mshr(), mvshr(), mand(), mvxor(), etc.

Hashing

mhash()

Aggregation

msum(), msumsqr(), mprod(), mmean(), mstdev(), etc.

Array Probing

mmax(), mcontains(), msubset(), etc.

5.3.5. Set and Copy

Table 19. Set and Copy Operations
Function Operation Ret Description

mset( m1, mn, x )

\( M_n := x \)

n

Assign numeric value x to all memory locations m1 to mn and return the number of memory locations set.

mreset()

\( [ 0 : -1 ] := 0 \)

n

Set all memory locations to integer 0 and free all temporary string memory consumed by any previous calls to string functions such as str(), join(), etc. Return the total number of memory locations.

mrandomize( m1, mn )

\( M_n := \text{random}() \)

n

Assign a different random value in the range [0.0 - 1.0] to each memory location m1 to mn and return the number of memory locations affected.

mrandbits( m1, mn )

\( M_n := \text{randbits}() \)

n

Assign a different random bit pattern to each memory location m1 to mn and return the number of memory locations affected.

mcopy( D, S, k )

\( [D_1 : D_k] := [S_1 : S_k] \)

k

Copy the contents of k consecutive source locations starting at S1 to the range of consecutive destinations starting at D1. If any Di or Si references a slot beyond the end of the memory array nothing is copied. The number of copied memory locations is returned.

mpwrite( pD, S, k )

\( [ [pD] : [pD] + (k-1) ] := [ S_1 : S_k] \)

then

\( [pD] := [pD] + k \)

k

Copy the contents of k consecutive source locations starting at S1 to the range of consecutive destinations starting at the memory location referenced by value in location pD, then increment the value stored in location pD by k. If any Si or the value at pD plus i - 1 references a slot beyond the end of the memory array nothing is copied. The number of copied memory locations is returned.

mcopyobj( D, obj )

\( [D_1 : D_n] := [obj_1 : obj_n] \)

where

\( n = \| obj \| \)

n

Copy the n raw quadwords of object obj into memory starting at location D1. The number of quadwords n comprising obj is determined automatically. If any Di references a slot beyond the end of the memory array nothing is copied.

mterm( m )

\( [m] := \text{null} \)

m

Set element at memory location m to null.

mlen( A )

\( \text{max}(k) \text{ such that} \)
\( \text{null} \notin [ A_1 : A_k ]\)

k

Return the number of elements between memory location A1 and the first occurrence of a null value or the end of the memory object if no null value is encountered.

5.3.6. Heap Operations

These functions implement binary heap operations useful for maintaining subsets of smallest or largest values from a greater set of candidates.

The min and max versions of these functions signify min-heap or max-heap semantics. Recall that a min-heap will discard smaller values as larger values are added, and that a max-heap will discard larger values as smaller values are added:

min-heap

Keep larger values

max-heap

Keep smaller values

Table 20. Heap Operations
Function Operation Ret Description

Initialize

mheapinit( A, k )

\( [A_1 : A_k] := \text{null} \)

k

Prepare array of k elements starting at location A for use with heap operations. The array is initialized with null-values that are interpreted as inf for max-heaps and -inf for min-heaps. The array satisfies both shape property and heap property for binary min and max heaps after this.

mheapifymin( A, k )

\( \text{heapifymin}( [ A_1 : A_k ] ) \)

k

Process array of k elements starting at location A such that the array satisfies the heap property for a min-heap.

mheapifymax( A, k )

\( \text{heapifymax}( [ A_1 : A_k ] ) \)

k

Process array of k elements starting at location A such that the array satisfies the heap property for a max-heap.

Populate

mheappushmin( A, k, x )

\(\text{if } [A_1] < x\ \text{, then}\)
\(\quad [A_1] := [A_k]\)
\(\quad [A_k] := x\)
\(\quad \text{downheapmin}( A, k-1 ) \)
\(\quad \text{upheapmin}( A, k-1 ) \)
\(\quad \text{return } 1\)

\(\quad \text{else}\)
\(\quad \text{return } 0\)

1 or 0

Attempt to insert numeric value x into binary min-heap represented by array of k elements starting at location A and return 1 if successful. Inserting a new value implies discarding the previous smallest value from the heap (if no null-values remain). If the new value is less than all values in the heap nothing is inserted and 0 is returned.

mheappushmax( A, k, x )

\(\text{if } [A_1] > x\ \text{, then}\)
\(\quad [A_1] := [A_k]\)
\(\quad [A_k] := x\)
\(\quad \text{downheapmax}( A, k-1 ) \)
\(\quad \text{upheapmax}( A, k-1 ) \)
\(\quad \text{return } 1\)

\(\quad \text{else}\)
\(\quad \text{return } 0\)

1 or 0

Attempt to insert numeric value x into binary max-heap represented by array of k elements starting at location A and return 1 if successful. Inserting a new value implies discarding the previous largest value from the heap (if no null-values remain). If the new value is greater than all values in the heap nothing is inserted and 0 is returned.

mheapwritemin( A, k, x1, x2, …​, xn )

\(\text{for all } x_i, i \in \{1,2,...,n\}\)
\(r_i = \text{mheappushmin}(A,k,x_i)\)

\(N = \displaystyle \sum_{i=1}^n r_i\)

N

Attempt to insert numeric values x1 through xn into binary min-heap represented by array of k elements starting at location A and return the number of values inserted. This is functionally equivalent to calling mheappushmin( A, k, xi) for all i from 1 through n and counting as N the number of xi successfully inserted.

mheapwritemax( A, k, x1, x2, …​, xn )

\(\text{for all } x_i, i \in \{1,2,...,n\}\)
\(r_i = \text{mheappushmax}(A,k,x_i)\)

\(N = \displaystyle \sum_{i=1}^n r_i\)

N

Attempt to insert numeric values x1 through xn into binary max-heap represented by array of k elements starting at location A and return the number of values inserted. This is functionally equivalent to calling mheappushmax( A, k, xi) for all i from 1 through n and counting as N the number of xi successfully inserted.

mheapsiftmin( A, k, m1, mn )

\(\text{for all } i \in \{1,2,...,n\}\)
mheappushmin(A,k,[mi])

n

Process all numeric values in memory locations m1 through mn by attempting to insert them into binary min-heap represented by array of k elements starting at location A and return the number of processed values, whether inserted or not. On completion the array A will contain the k largest values from the source range.

mheapsiftmax( A, k, m1, mn )

\(\text{for all } i \in \{1,2,...,n\}\)
mheappushmax(A,k,[mi])

n

Process all numeric values in memory locations m1 through mn by attempting to insert them into binary max-heap represented by array of k elements starting at location A and return the number of processed values, whether inserted or not. On completion the array A will contain the k smallest values from the source range.

5.3.7. Sorting

Table 21. Array Sorting
Function Operation Ret Description

msort( A, k )

sortascending( [ A1 : Ak ] )

nk

Sort array of k values starting at location A in ascending order, in-place. Any null-value compares greater-than any numeric value, and will appear at the end of the sorted array. The number n of non-null values is returned. (All other non-real elements are interpreted as integers.)

msortrev( A, k )

sortdescending( [ A1 : Ak ] )

nk

Sort array of k values starting at location A in descending order, in-place. Any null-value compares less-than any numeric value, and will appear at the end of the sorted array. The number n of non-null values is returned. (All other non-real elements are interpreted as integers.)

mrsort( A, k )

sortascending( [ A1 : Ak ] )
( assumes isreal( Ai ) == true )

k

Sort array of k values (interpreted as floating point) starting at location A in ascending order, in-place.

mrsortrev( A, k )

sortdescending( [ A1 : Ak ] )
( assumes isreal( Ai ) == true )

k

Sort array of k values (interpreted as floating point) starting at location A in descending order, in-place.

mreverse( A, k )

reverse( [A1 : Ak] )

k

Reverse array of k objects starting at location A, in-place.

5.3.8. Type Cast

Table 22. Array Type Cast
Function Operation Ret Description

mint( m1, mn )

\( M_n := \text{integer}( M_n ) \)

n

Cast all values in memory locations m1 to mn to integer and return the number of memory locations affected by the operation.

mintr( m1, mn )

\( M_n := \text{integer}( \lfloor M_n \rceil ) \)

n

Cast all values in memory locations m1 to mn to integer after rounding to the nearest whole number and return the number of memory locations affected by the operation.

mreal( m1, mn )

\( M_n := \text{real}( M_n ) \)

n

Cast all values in memory locations m1 to mn to floating point and return the number of memory locations affected by the operation.

mbits( m1, mn )

\( M_n := \text{real}( M_n ) \)

n

Cast all values in memory locations m1 to mn to floating point and return the number of memory locations affected by the operation.

5.3.9. Increment and Decrement

Table 23. Array Increment and Decrement
Function Operation Ret Description

Increment

minc( m1, mn )

\( M_n := M_n + 1 \)

n

Increment all values in memory locations m1 to mn and return the number of memory locations affected by the operation.

miinc( m1, mn )

\( M_n := M_n + 1 \)

n

Increment all values in memory locations m1 to mn (interpreted as integers) and return the number of memory locations affected by the operation.

mrinc( m1, mn )

\( M_n := M_n + 1 \)

n

Increment all values in memory locations m1 to mn (interpreted as floating point) and return the number of memory locations affected by the operation.

Decrement

mdec( m1, mn )

\( M_n := M_n - 1 \)

n

Decrement all values in memory locations m1 to mn and return the number of memory locations affected by the operation.

midec( m1, mn )

\( M_n := M_n - 1 \)

n

Decrement all values in memory locations m1 to mn (interpreted as integers) and return the number of memory locations affected by the operation.

mrdec( m1, mn )

\( M_n := M_n - 1 \)

n

Decrement all values in memory locations m1 to mn (interpreted as floating point) and return the number of memory locations affected by the operation.

5.3.10. Arithmetic

Table 24. Array Arithmetic
Function Operation Ret Description

Add

madd( m1, mn, x )

\( M_n := M_n + x \)

n

Add x to all values in memory locations m1 to mn and return the number of memory locations affected by the operation.

miadd( m1, mn, x )

\( M_n := M_n + x \)

n

Add integer x to all values in memory locations m1 to mn (interpreted as integers) and return the number of memory locations affected by the operation.

mradd( m1, mn, x )

\( M_n := M_n + x \)

n

Add floating point value x to all values in memory locations m1 to mn (interpreted as floating point) and return the number of memory locations affected by the operation.

mvadd( A, B, k )

[ A1 : Ak ] := [ A1 + B1 : Ak + Bk ]

k

For all i from 1 through k, add numeric values in memory locations Ai and Bi and store the result in location Ai, then return k.

Subtract

msub( m1, mn, x )

\( M_n := M_n - x \)

n

Subtract x from all values in memory locations m1 to mn and return the number of memory locations affected by the operation.

misub( m1, mn, x )

\( M_n := M_n - x \)

n

Subtract integer x from all values in memory locations m1 (interpreted as integers) to mn and return the number of memory locations affected by the operation.

mrsub( m1, mn, x )

\( M_n := M_n - x \)

n

Subtract floating point value x from all values in memory locations m1 to mn (interpreted as floating point) and return the number of memory locations affected by the operation.

mvsub( A, B, k )

[ A1 : Ak ] := [ A1B1 : AkBk ]

k

For all i from 1 through k, subtract numeric value in memory location Bi from numeric value in memory location Ai and store the result in location Ai, then return k.

Multiply

mmul( m1, mn, x )

\( M_n := M_n \cdot x \)

n

Multiply all values in memory locations m1 to mn by x and return the number of memory locations affected by the operation.

mimul( m1, mn, x )

\( M_n := M_n \cdot x \)

n

Multiply all values in memory locations m1 to mn (interpreted as integers) by integer x and return the number of memory locations affected by the operation.

mrmul( m1, mn, x )

\( M_n := M_n \cdot x \)

n

Multiply all values in memory locations m1 to mn (interpreted as floating point) by floating point value x and return the number of memory locations affected by the operation.

mvmul( A, B, k )

[ A1 : Ak ] := [ A1 * B1 : Ak * Bk ]

k

For all i from 1 through k, multiply numeric values in memory locations Ai and Bi and store the result in location Ai, then return k.

Divide

mdiv( m1, mn, x )

\( M_n := \displaystyle \frac{M_n}{x} \)

n

Divide all values in memory locations m1 to mn by x and return the number of memory locations affected by the operation.

midiv( m1, mn, x )

\( M_n := \displaystyle \frac{M_n}{x} \)

n

Divide all values in memory locations m1 to mn (interpreted as integers) by integer x and return the number of memory locations affected by the operation.

mrdiv( m1, mn, x )

\( M_n := \displaystyle \frac{M_n}{x} \)

n

Divide all values in memory locations m1 to mn (interpreted as floating point) by floating point value x and return the number of memory locations affected by the operation.

mvdiv( A, B, k )

[ A1 : Ak ] := [ A1 / B1 : Ak / Bk ]

k

For all i from 1 through k, divide numeric value in memory location Ai by numeric value in memory location Bi and store the result in location Ai, then return k.

Modulo

mmod( m1, mn, x )

\( M_n := M_n \mod x \)

n

Perform modulo by x operation on all values in memory locations m1 to mn and return the number of memory locations affected by the operation.

mimod( m1, mn, x )

\( M_n := M_n \mod x \)

n

Perform integer modulo by x on all values in memory locations m1 to mn (interpreted as integers) and return the number of memory locations affected by the operation.

mrmod( m1, mn, x )

\( M_n := M_n \mod x \)

n

Perform floating point modulo by x on all values in memory locations m1 to mn (interpreted as floating point) and return the number of memory locations affected by the operation.

mvmod( A, B, k )

[ A1 : Ak ] := [ A1 % B1 : Ak % Bk ]

k

For all i from 1 through k, perform [Ai] modulo [Bi] and store the result in location Ai, then return k.

Inverse

minv( m1, mn )

\( M_n := \displaystyle \frac{1}{M_n} \)

n

Perform the inverse operation on all values in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrinv( m1, mn )

\( M_n := \displaystyle \frac{1}{M_n} \)

n

Perform the inverse operation on all values in memory locations m1 to mn interpreted as floating point values and return the number of memory locations affected by the operation.

Power

mpow( m1, mn, y )

\( M_n := (M_n)^y \)

n

Raise all values in memory locations m1 to mn to the yth power and return the number of memory locations affected by the operation.

mrpow( m1, mn, y )

\( M_n := (M_n)^y \)

n

Raise all values (interpreted as floating point values) in memory locations m1 to mn to the yth power and return the number of memory locations affected by the operation.

Square

msq( m1, mn )

\( M_n := (M_n)^2 \)

n

Square all values in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrsq( m1, mn )

\( M_n := (M_n)^2 \)

n

Square all values (interpreted as floating point values) in memory locations m1 to mn and return the number of memory locations affected by the operation.

Square Root

msqrt( m1, mn )

\( M_n := \displaystyle \sqrt{M_n} \)

n

Take the square root of all values in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrsqrt( m1, mn )

\( M_n := \displaystyle \sqrt{M_n} \)

n

Take the square root of all values (interpreted as floating point values) in memory locations m1 to mn and return the number of memory locations affected by the operation.

5.3.11. Numeric

Table 25. Array Numeric Functions
Function Operation Ret Description

Ceil

mceil( m1, mn )

\( M_n := \lceil M_n \rceil \)

n

Set all values in memory locations m1 to mn to their floating point values representing the smallest integers greater than or equal to their values. Return the number of memory locations affected by the operation.

mrceil( m1, mn )

\( M_n := \lceil M_n \rceil \)

n

Set all values (interpreted as floating point values) in memory locations m1 to mn to their floating point values representing the smallest integers greater than or equal to their values. Return the number of memory locations affected by the operation.

Floor

mfloor( m1, mn )

\( M_n := \lfloor M_n \rfloor \)

n

Set all values in memory locations m1 to mn to their floating point values representing the largest integers less than or equal to their values. Return the number of memory locations affected by the operation.

mrfloor( m1, mn )

\( M_n := \lfloor M_n \rfloor \)

n

Set all values (interpreted as floating point values) in memory locations m1 to mn to their floating point values representing the largest integers less than or equal to their values. Return the number of memory locations affected by the operation.

Round

mround( m1, mn )

\( M_n := \lfloor M_n \rceil \)

n

Round all values in memory locations m1 to mn to the real values representing their nearest integers and return the number of memory locations affected by the operation.

NOTE: Rounding rule is to round away from zero. (Differs from Python3 round-to-even rule!)

mrround( m1, mn )

\( M_n := \lfloor M_n \rceil \)

n

Round all values (interpreted as floating point values) in memory locations m1 to mn to the real values representing their nearest integers and return the number of memory locations affected by the operation.

Absolute Value

mabs( m1, mn )

\( M_n := | M_n | \)

n

Convert all values in memory locations m1 to mn to their absolute values and return the number of memory locations affected by the operation.

mrabs( m1, mn )

\( M_n := | M_n | \)

n

Convert all values (interpreted as floating point) in memory locations m1 to mn to their absolute values and return the number of memory locations affected by the operation.

Sign

msign( m1, mn )

\( M_n := \sgn(M_n) \)

\( \sgn(x) = \begin{cases} 1 & \quad \text{if } x > 0 \\ 0 & \quad \text{if } x = 0 \\ -1 & \quad \text{if } x < 0 \end {cases} \)

n

Replace all values in memory locations m1 to mn with their signs as 1 or -1, and return the number of memory locations affected by the operation.

mrsign( m1, mn )

\( M_n := \sgn(M_n) \)

n

Replace all values (interpreted as floating point) in memory locations m1 to mn with their signs as 1.0 or -1.0, and return the number of memory locations affected by the operation.

5.3.12. Logarithms and Exponential Functions

Table 26. Array Logarithm and Exponential Functions
Function Operation Ret Description

log2

mlog2( m1, mn )

\( M_n := \log_2(M_n) \)

n

Compute the base-2 logarithm of values in memory locations m1 to mn. Return the number of memory locations affected by the operation.

mrlog2( m1, mn )

\( M_n := \log_2(M_n) \)

n

Compute the base-2 logarithm of values (interpreted as floating point) in memory locations m1 to mn. Return the number of memory locations affected by the operation.

Natural Logarithm

mlog( m1, mn )

\( M_n := \ln(M_n) \)

n

Compute the natural logarithm of values in memory locations m1 to mn. Return the number of memory locations affected by the operation.

mrlog( m1, mn )

\( M_n := \ln(M_n) \)

n

Compute the natural logarithm of values (interpreted as floating point) in memory locations m1 to mn. Return the number of memory locations affected by the operation.

log10

mlog10( m1, mn )

\( M_n := \log_{10}(M_n) \)

n

Compute the base-10 logarithm of values in memory locations m1 to mn. Return the number of memory locations affected by the operation.

mrlog10( m1, mn )

\( M_n := \log_{10}(M_n) \)

n

Compute the base-10 logarithm of values (interpreted as floating point) in memory locations m1 to mn. Return the number of memory locations affected by the operation.

exp2

mexp2( m1, mn )

\( M_n := 2^{M_n} \)

n

Perform 2x for all values x in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrexp2( m1, mn )

\( M_n := 2^{M_n} \)

n

Perform 2x for all values x (interpreted as floating point values) in memory locations m1 to mn and return the number of memory locations affected by the operation.

Exponentiation

mexp( m1, mn )

\( M_n := e^{M_n} \)

n

Perform ex for all values x in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrexp( m1, mn )

\( M_n := e^{M_n} \)

n

Perform ex for all values x (interpreted as floating point values) in memory locations m1 to mn and return the number of memory locations affected by the operation.

exp10

mexp10( m1, mn )

\( M_n := 10^{M_n} \)

n

Perform 10x for all values x in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrexp10( m1, mn )

\( M_n := 10^{M_n} \)

n

Perform 10x for all values x (interpreted as floating point values) in memory locations m1 to mn and return the number of memory locations affected by the operation.

5.3.13. Trigonometric and Hyperbolic

Table 27. Array Trigonometric and Hyperbolic Functions
Function Operation Ret Description

Angle Conversion

mrad( m1, mn )

\( M_n := \displaystyle \frac{\pi}{180} \cdot M_n \)

n

Convert all values (assumed to be degrees) in memory locations m1 to mn to radians and return the number of memory locations affected by the operation.

mrrad( m1, mn )

\( M_n := \displaystyle \frac{\pi}{180} \cdot M_n \)

n

Convert all values (assumed to be degrees and interpreted as floating point) in memory locations m1 to mn to radians and return the number of memory locations affected by the operation.

mdeg( m1, mn )

\( M_n := \displaystyle \frac{180}{\pi} \cdot M_n \)

n

Convert all values (assumed to be radians) in memory locations m1 to mn to degrees and return the number of memory locations affected by the operation.

mrdeg( m1, mn )

\( M_n := \displaystyle \frac{180}{\pi} \cdot M_n \)

n

Convert all values (assumed to be radians and interpreted as floating point) in memory locations m1 to mn to degrees and return the number of memory locations affected by the operation.

Trigonometric

msin( m1, mn )
mcos( m1, mn )
mtan( m1, mn )

\( M_n := \sin(M_n) \)
\( M_n := \cos(M_n) \)
\( M_n := \tan(M_n) \)

n

Compute sin(x), cos(x) or tan(x) for all x in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrsin( m1, mn )
mrcos( m1, mn )
mrtan( m1, mn )

\( M_n := \sin(M_n) \)
\( M_n := \cos(M_n) \)
\( M_n := \tan(M_n) \)

n

Compute sin(x), cos(x) or tan(x) for all x (interpreted as floating point) in memory locations m1 to mn and return the number of memory locations affected by the operation.

Inverse Trigonometric

masin( m1, mn )
macos( m1, mn )
matan( m1, mn )

\( M_n := \arcsin(M_n) \)
\( M_n := \arccos(M_n) \)
\( M_n := \arctan(M_n) \)

n

Compute asin(x), acos(x) or atan(x) for all x in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrasin( m1, mn )
mracos( m1, mn )
mratan( m1, mn )

\( M_n := \arcsin(M_n) \)
\( M_n := \arccos(M_n) \)
\( M_n := \arctan(M_n) \)

n

Compute asin(x), acos(x) or atan(x) for all x (interpreted as floating point) in memory locations m1 to mn and return the number of memory locations affected by the operation.

Hyperbolic

msinh( m1, mn )
mcosh( m1, mn )
mtanh( m1, mn )

\( M_n := \sinh(M_n) \)
\( M_n := \cosh(M_n) \)
\( M_n := \tanh(M_n) \)

n

Compute sinh(x), cosh(x) or tanh(x) for all x in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrsinh( m1, mn )
mrcosh( m1, mn )
mrtanh( m1, mn )

\( M_n := \sinh(M_n) \)
\( M_n := \cosh(M_n) \)
\( M_n := \tanh(M_n) \)

n

Compute sinh(x), cosh(x) or tanh(x) for all x (interpreted as floating point) in memory locations m1 to mn and return the number of memory locations affected by the operation.

Inverse Hyperbolic

masinh( m1, mn )
macosh( m1, mn )
matanh( m1, mn )

\( M_n := \arcsinh(M_n) \)
\( M_n := \arccosh(M_n) \)
\( M_n := \arctanh(M_n) \)

n

Compute asinh(x), acosh(x) or atanh(x) for all x in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrasinh( m1, mn )
mracosh( m1, mn )
mratanh( m1, mn )

\( M_n := \arcsinh(M_n) \)
\( M_n := \arccosh(M_n) \)
\( M_n := \arctanh(M_n) \)

n

Compute asinh(x), acosh(x) or atanh(x) for all x (interpreted as floating point) in memory locations m1 to mn and return the number of memory locations affected by the operation.

Sinc

msinc( m1, mn )

\( M_n := \displaystyle \frac{\sin{ \pi M_n }}{\pi M_n} \)

n

Compute sin( πx ) / πx for all x in memory locations m1 to mn and return the number of memory locations affected by the operation.

mrsinc( m1, mn )

\( M_n := \displaystyle \frac{\sin{ \pi M_n }}{\pi M_n} \)

n

Compute sin( πx ) / πx for all x (interpreted as floating point) in memory locations m1 to mn and return the number of memory locations affected by the operation.

5.3.14. Bitwise Operations

Table 28. Array Bitwise Functions
Function Operation Ret Description

Shift

mshr( m1, mn, b )

\( M_n := M_n \text{ >> } b \)

where

\( a \text{ >> } b \quad\) shifts integer \(a\) right by \(b\) bit positions.

n

Shift all values in memory locations m1 to mn right by b bits and return the number of memory locations affected by the operation.

mvshr( A, B, k )

[ A1 : Ak ] := [ A1 >> B1 : Ak >> Bk ]

k

For all i from 1 through k, shift integer value in memory location Ai right by the number of bits indicated by integer value in memory location Bi and store the result in location Ai, then return k.

mshl( m1, mn, b )

\( M_n := M_n \text{ << } b \)

where

\( a \text{ << } b \quad\) shifts integer \(a\) left by \(b\) bit positions.

n

Shift all values in memory locations m1 to mn left by b bits and return the number of memory locations affected by the operation.

mvshl( A, B, k )

[ A1 : Ak ] := [ A1 << B1 : Ak << Bk ]

k

For all i from 1 through k, shift integer value in memory location Ai left by the number of bits indicated by integer value in memory location Bi and store the result in location Ai, then return k.

Logical

mand( m1, mn, x )

\( M_n := M_n \text{ & } x \)

where

\( a \text{ & } x \quad\) performs bitwise AND on integers \(a \text{ and } x\).

n

Perform bitwise-AND operation on all values in memory locations m1 to mn using mask x and return the number of memory locations affected by the operation.

mvand( A, B, k )

[ A1 : Ak ] := [ A1 & B1 : Ak & Bk ]

k

For all i from 1 through k, perform bitwise-AND on integer values in memory locations Ai and Bi and store the result in location Ai, then return k.

mor( m1, mn, x )

\( M_n := M_n \text{ | } x \)

where

\( a \text{ | } x \quad\) performs bitwise OR on integers \(a \text{ and } x\).

n

Perform bitwise-OR operation on all values in memory locations m1 to mn using mask x and return the number of memory locations affected by the operation.

mvor( A, B, k )

[ A1 : Ak ] := [ A1 | B1 : Ak | Bk ]

k

For all i from 1 through k, perform bitwise-OR on integer values in memory locations Ai and Bi and store the result in location Ai, then return k.

mxor( m1, mn, x )

\( M_n := M_n \text{ ^ } x \)

where

\( a \text{ ^ } x \quad\) performs bitwise XOR on integers \(a \text{ and } x\).

n

Perform bitwise-XOR operation on all values in memory locations m1 to mn using mask x and return the number of memory locations affected by the operation.

mvxor( A, B, k )

[ A1 : Ak ] := [ A1 ^ B1 : Ak ^ Bk ]

k

For all i from 1 through k, perform bitwise-XOR on integer values in memory locations Ai and Bi and store the result in location Ai, then return k.

Other

mpopcnt( m1, mn )

\( M_n := popcnt(M_n) \)

where

\( popcnt(a) \) is the number of bits set to 1 in the binary representation of \(a\).

n

Count the number of bits set to 1 for all values in memory locations m1 to mn, update all locations with their respective bit counts and return the number of memory locations affected.

5.3.15. Hashing

Table 29. Array Hashing
Function Operation Ret Description

mhash( m1, mn )

\( M_n := h(M_n) \)

where

\( h : x \mapsto y \)
such that \(y\) has the properties of a uniformly distributed random number with respect to \(x\).

n

Compute the 64-bit integer hash of all values in memory locations m1 to mn, update all locations with their hash value and return the number of memory locations affected.

5.3.16. Aggregation

Table 30. Array Aggregation
Function Operation Ret Description

Sum

msum( dest, m1, mn )

\([dest] := \displaystyle \sum M_n \)

[dest]

Add all numeric values in memory locations m1 through mn and assign the resulting sum to memory location dest as a real value then return the result.

mrsum( dest, m1, mn )

\([dest] := \displaystyle \sum M_n \)

[dest]

Add all values (interpreted as floating point) in memory locations m1 through mn and assign the resulting sum to memory location dest as a real value then return the result.

msumsqr( dest, m1, mn )

\([dest] := \displaystyle \sum (M_n)^2 \)

[dest]

Add the square of all numeric values in memory locations m1 through mn and assign the resulting sum to memory location dest then return the result.

mrsumsqr( dest, m1, mn )

\([dest] := \displaystyle \sum (M_n)^2 \)

[dest]

Add the square of all values (interpreted as floating point) in memory locations m1 through mn and assign the resulting sum to memory location dest then return the result.

minvsum( dest, m1, mn )

\([dest] := \displaystyle \sum \frac{1}{M_n} \)

[dest]

Add the inverse of all numeric values in memory locations m1 through mn and assign the resulting sum to memory location dest then return the result.

mrinvsum( dest, m1, mn )

\([dest] := \displaystyle \sum \frac{1}{M_n} \)

[dest]

Add the inverse of all values (interpreted as floating point) in memory locations m1 through mn and assign the resulting sum to memory location dest then return the result.

Product

mprod( dest, m1, mn )

\([dest] := \displaystyle \prod M_n \)

[dest]

Multiply all numeric values in memory locations m1 through mn and assign the resulting product to memory location dest then return the result.

mrprod( dest, m1, mn )

\([dest] := \displaystyle \prod M_n \)

[dest]

Multiply all values (interpreted as floating point) in memory locations m1 through mn and assign the resulting product to memory location dest then return the result.

Mean

mmean( dest, m1, mn )

\([dest] := \displaystyle \frac{1}{n} \sum M_n \)

[dest]

Compute the arithmetic mean (average) of all numeric values in memory locations m1 through mn and assign the result to memory location dest then return the result.

mrmean( dest, m1, mn )

\([dest] := \displaystyle \frac{1}{n} \sum M_n \)

[dest]

Compute the arithmetic mean (average) of all values (interpreted as floating point) in memory locations m1 through mn and assign the result to memory location dest then return the result.

mharmmean( dest, m1, mn )

\([dest] := \displaystyle \left(\frac{1}{n} \sum {\frac{1}{M_n}} \right)^{-1} \)

[dest]

Compute the harmonic mean of all numeric values in memory locations m1 through mn and assign the result to memory location dest then return the result.

mrharmmean( dest, m1, mn )

\([dest] := \displaystyle \left(\frac{1}{n} \sum {\frac{1}{M_n}} \right)^{-1} \)

[dest]

Compute the harmonic mean of all values (interpreted as floating point) in memory locations m1 through mn and assign the result to memory location dest then return the result.

mgeomean( dest, m1, mn )

\([dest] := \displaystyle \sqrt[n]{\prod{M_n}} \)

[dest]

Compute the geometric mean of all numeric values in memory locations m1 through mn and assign the result to memory location dest then return the result.

mrgeomean( dest, m1, mn )

\([dest] := \displaystyle \sqrt[n]{\prod{M_n}} \)

[dest]

Compute the geometric mean of all values (interpreted as floating point) in memory locations m1 through mn and assign the result to memory location dest then return the result.

Deviation

mstdev( dest, m1, mn )

\( [dest] := \displaystyle \sqrt{ \frac{1}{n} \displaystyle \sum_{i=1}^n {(x_i-\mu)^2} } \)

where

\( x_i = \text{value at location } m_i\)
and
\( \mu = \displaystyle \frac{1}{n} \sum M_n\)

[dest]

Compute the standard deviation of numeric values in memory locations m1 through mn and assign the result to memory location dest then return the result.

mrstdev( dest, m1, mn )

\( [dest] := \displaystyle \sqrt{ \frac{1}{n} \displaystyle \sum_{i=1}^n {(x_i-\mu)^2} } \)

[dest]

Compute the standard deviation of values (interpreted as floating point) in memory locations m1 through mn and assign the result to memory location dest then return the result.

mgeostdev( dest, m1, mn )

\( [dest] := \exp \left(\displaystyle \sqrt{\displaystyle \frac{\displaystyle \sum_{i=1}^n{\left(\ln{\frac{x_i}{\mu_g}}\right)^2}}{n}}\right) \)

where

\( x_i = \text{value at location } m_i\)
and
\( \mu_g = \displaystyle \sqrt[n]{\prod M_n} \)

[dest]

Compute the geometric standard deviation of numeric values in memory locations m1 through mn and assign the result to memory location dest then return the result.

mrgeostdev( dest, m1, mn )

\( [dest] := \exp \left(\displaystyle \sqrt{\displaystyle \frac{\displaystyle \sum_{i=1}^n{\left(\ln{\frac{x_i}{\mu_g}}\right)^2}}{n}}\right) \)

[dest]

Compute the geometric standard deviation of values (interpreted as floating point) in memory locations m1 through mn and assign the result to memory location dest then return the result.

5.3.17. Array Probing

Table 31. Array Probing
Function Operation Ret Description

Simple

mmax( dest, m1, mn )

\( [dest] := \max( M_n ) \)

mk

Find the largest value in memory locations m1 through mn and assign it to memory location dest, then return the location mk where the largest value was found.

mmin( dest, m1, mn )

\( [dest] := \min( M_n ) \)

mk

Find the smallest value in memory locations m1 through mn and assign it to memory location dest, then return the location mk where the smallest value was found.

mcontains( m1, mn, value )

\( y = \begin{cases} 1 & \quad \text{if value } \in M_n \\ 0 & \quad \text{if value } \notin M_n \end{cases} \)

y

Return 1 if value occurs in any memory location m1 through mn, otherwise return 0.

mcount( m1, mn, value )

\( y = | { x \in M_n : x = \text{value} } | \)

y

Return the number of times value occurs in memory location range m1 through mn.

mcmp( A, B, k )

\( y = cmp( [ A_1 : A_k ], [ B_1 : B_k ] ) \)

\( cmp(a,b) = \begin{cases} 0 & \quad \text{if } a = b \\ 1 & \quad \text{if } a > b \\ -1 & \quad \text{if } a < b \end{cases}\)

y

For all i from 1 through k, compare numeric values in memory locations Ai and Bi. Immediately return -1 at the first iteration where [Ai] < [Bi] or 1 at the first iteration where [Ai] > [Bi]. If [Ai] == [Bi] for all i return 0. If k is such that either A or B extends beyond the end of memory -1 is returned.

mcmpa( A, B, k )

\( y = cmp( [ A_1 : A_k ], [ B_1 : B_k ] ) \)

Element-wise comparison function defined as:

\( cmp(a,b) = \begin{cases} 0 & \quad \text{if } \delta < \varepsilon \\ 1 & \quad \text{if } a > b \wedge \delta \geq \varepsilon \\ -1 & \quad \text{if } a < b \wedge \delta \geq \varepsilon \end{cases}\)

where \( \delta = |a-b| \)
and \( \varepsilon = 1.1921 \cdot 10^{-7} \)

y

For all i from 1 through k, compare numeric values in memory locations Ai and Bi using approximate matching for real values. Real values a and b compare equal when \( |a-b| < \varepsilon \). Immediately return -1 at the first iteration where [Ai] < [Bi] or 1 at the first iteration where [Ai] > [Bi]. If [Ai] == [Bi] for all i return 0. If k is such that either A or B extends beyond the end of memory -1 is returned.

mindex( m1, mn, value )

\( k = \min( i ), i \in [1,n] \text{ such that} \)
\( value \in [m_1 : m_i]\)

mk or -1

Return first memory location mk where value occurs in range m1 through mn. If value does not occur in specified range return -1.

Subset

msubset( p1, pk, m1, mn )

\( y = \begin{cases} 1 & \quad \text{if } P_k \in M_n \\ 0 & \quad \text{if } P_k \notin M_n \end{cases} \)

\( k \leq n \)

1 or 0

Return 1 if all values in probe memory location range p1 through pk occur in memory location range m1 through mn, otherwise return 0.

msubsetobj( p1, pk, obj )

\( y = \begin{cases} 1 & \quad \text{if } P_k \in obj \\ 0 & \quad \text{if } P_k \notin obj \end{cases} \)

\( k \leq | obj | \)

1 or 0

Return 1 if all values in probe memory location range p1 through pk occur in obj, otherwise return 0. The object obj is either a numeric array or a map, i.e. an object for which isarray() or ismap() is true. If obj is a numeric array its values must be integers.

5.4. Mappings and Sets

These functions operate on built-in maps and sets associated with memory objects.

The integer set and vertex set functions operate on the same internal set structure. Mixing iset and vset operations will most certainly yield unexpected results.

5.4.1. Integer Set

The integer set is used to store and check existence of unique (32-bit) integer keys. Object key may be of any type, but its raw bit value (as produced by asbits(key)) is always implied and only the 32 lower bits are used.

Key 0xffffffff is reserved and cannot be used.

Function Return Value Description

iset.add( key )

\( y = \begin{cases} 1 & \quad \text{key added} \\ 0 & \quad \text{already exists} \\ -1 & \quad \text{error} \end{cases} \)

Add key to set and return 1 if added or 0 if key already existed in the set. If the value could not be added -1 is returned.

iset.del( key )

\( y = \begin{cases} 1 & \quad \text{key deleted} \\ 0 & \quad \text{does not exist} \end{cases} \)

Delete key from set and return 1 if deleted or 0 if key did not exist in the set.

iset.has( key )

\( y = \begin{cases} 1 & \quad \text{key exists} \\ 0 & \quad \text{does not exist} \end{cases} \)

Return 1 if key exists in set, otherwise return 0.

iset.len()

number of mapped keys

Return the number of keys in set.

iset.clr()

number of removed keys

Remove all keys from set and return the number of keys removed.

5.4.2. Vertex Set

The vertex set is used to store and check existence of unique vertex instances.

Function Return Value Description

vset.add( vertex )

\( y = \begin{cases} 1 & \quad \text{vertex added} \\ 0 & \quad \text{already exists} \\ -1 & \quad \text{error} \end{cases} \)

Add vertex to set and return 1 if added or 0 if vertex already existed in the set. If the vertex could not be added -1 is returned.

vset.del( vertex )

\( y = \begin{cases} 1 & \quad \text{vertex deleted} \\ 0 & \quad \text{does not exist} \end{cases} \)

Delete vertex from set and return 1 if deleted or 0 if vertex did not exist in the set.

vset.has( vertex )

\( y = \begin{cases} 1 & \quad \text{vertex exists} \\ 0 & \quad \text{does not exist} \end{cases} \)

Return 1 if vertex exists in set, otherwise return 0.

vset.len()

number of vertices

Return the number of vertices in set.

vset.clr()

number of vertices

Remove all vertices from set and return the number of vertices removed.

6. Special Purpose Operations

This section describes specialized operations for solving specific problems.

Table 32. Quick Links
Function Description

sumprodobj()

probealtarray()

Compare array object to probe stored in memory range and compute match score

synarc.decay() and synarc.xdecay()

Time-dependent arc values

6.1. sumprodobj()

Compute the sum of products of all matching pairs of items in object obj and memory location range \( [p_1 : p_k] \).

The object obj is either a numeric array or a map. If obj is a numeric array its values must be integers.

The items to be matched are compared using key function \(K(x)\) which is either the integer key of keyval item x, or x itself if x is an integer.

The item value \( V(x) \) is either the float value of keyval item x, or 1.0 if x is an integer.

6.1.1. Syntax

msumprodobj( p1, pk, obj ) -> y

6.1.2. Parameters

Parameter Type Description

p1

integer

First memory location of probe item

pk

integer

Last memory location of probe item

obj

numeric array or map

Numeric array objects are sequences of integers, such as vertex properties assigned as V.SetProperty( "A", [10,20,30,40] ). Map object are sets of keyval items, such as vertex properties assigned as V.SetProperty( "M", {1:2.34, 5:6.78} ).

6.1.3. Return Value

This function returns a floating point value representing the sum of products of item values for all items that occur in both obj and memory range \( [p_1 : p_k] \).

6.1.4. Remarks

Items to be matched and whose values are contributing to the sum must be plain integers or keyval items.

Memory probe items are assigned to the memory object using the respective data type syntax.

Integer item in memory probe location

m[i] = integer_value

Item key is the assigned integer, and item has implicit value 1.0.

Keyval item in memory probe location

m[i] = (integer_key, float_value)

Item key is the tuple’s integer_key, and item value is the tuple’s float_value.

Memory probe example
from pyvgx import *
g = Graph("test")
m = g.Memory(16)

# Assign integer items
m[0] = 123          # key=123, val=1.0
m[1] = 456          # key=456, val=1.0
m[2] = 789          # key=789, val=1.0

# Assign keyval items
m[3] = (200, 2.718) # key=200, val=2.718
m[4] = (300, 3.142) # key=300, val=3.142

Objects are typically vertex properties and are assigned according to property assignment syntax.

Numeric array in vertex property

V[prop] = [v1, v2, …​]

The item key of each integer vi is the integer itself, and the item has implicit value 1.0.

Keyval map in vertex property

V[prop] = { k1:v1, k2:v2, …​ }

The item key of each ki : vi pair is integer key ki, and the item value is float vi.

Object example (vertex property)
from pyvgx import *
g = Graph("test")
V = g.NewVertex( "V" )

# Assign array of three integers to property 'p1'
V['p1'] = [123, 456, 789]

# Assign map of two int:float items to property 'p2'
V['p2'] = {200:2.718, 300:3.142}

6.1.5. Formula

The return value s is computed as follows.

\( s = \displaystyle \sum_{i=1}^k{ \left[ V(p_i) \sum_{j=1}^n{ \bigg( V(x_j) \: \big| \: x_j \in obj \wedge M(p_i,x_j) \bigg) } \right] }\)

where

\( V(x) = \begin{cases} \text{value of x} & \quad \text{if x is keyval} \\ 1 & \quad \text{if x is integer} \end{cases} \)

and

\( M(a,b) = \begin{cases} true & \quad \text{if } K(a) = K(b) \\ false & \quad \text{if } K(a) \ne K(b) \end{cases} \)

and

\( K(x) = \begin{cases} \text{key of x} & \quad \text{if x is keyval} \\ x & \quad \text{if x is integer} \end{cases} \)

and

\( n = | obj | \)

6.1.6. Example

from pyvgx import *
g = Graph("test")
m = g.Memory(16)

# Vertex with array and map properties
#V = g.NewVertex("V")
V['p1'] = [123, 456, 789]
V['p2'] = {200:2.718, 300:3.142}

# Probe items
m[0] = 123
m[1] = 456
m[2] = 789
m[3] = (200, 2.718)
m[4] = (300, 3.142)

# Probe against numeric array object will
# match three integer items and
# return 3.0
g.Evaluate( "msumprodobj(0,4,next['p1'])", memory=m, head="V" )

# Probe against keyval map object will
# match two keyval items and
# return 17.26
g.Evaluate( "msumprodobj(0,4,next['p2'])", memory=m, head="V" )

6.2. probealtarray()

Test if all elements of a probe stored in memory range [p1, pn] exist in array object altarray and return a position-dependent and match-type dependent score. The array can have up to 16 elements. Array elements are dual-value integers, divided into a primary region and a secondary (alternative) region. For a probe element and an array element to match their primary regions must be equal, or their secondary regions must be equal. A probe element may also be a dual bitvector split into a primary and secondary region for cases where the probe is already known to match the array object in one or more positions, but needs to be included for keeping track of number of matches and their positions.

6.2.1. Syntax

probealtarray( p1, pn, altarray, prefix_boost, alt_deboost ) -> score

6.2.2. Parameters

Parameter Type Description

p1, pn

2 x 32-bit integers
or
2 x 16-bit bitvectors

Memory locations 1, 2, …​, n of the probe holding dual-value integers or dual bitvectors

altarray

Numeric array object (multi-valued vertex property)

Array object to be probed, containing up to 16 dual-value integers.

prefix_boost

real

Score multiplier for ordered match starting from the first element of altarray

alt_deboost

real

Score multiplier for match involving one or more matches on secondary (alternative) element regions

altarray: The values in altarray are presumed to be encoded as two 32-bit integers packed into a 64-bit integer. This special encoding will be referred to as dual-value integer. The upper 32 bits represent the primary region. The lower 32 bits represent the secondary (or alternative) region. This makes it possible for two distinct values to occupy the same position in altarray, enabling position-aware matching with alternative scoring depending on which region of the element is matched.

[p1, pn]: A probe element is either a dual-value integer encoded the same way as values in altarray, or a dual bitvector. A bitvector in this context uses only the 32 lower bits, subdivided into a 16-bit primary region (bits 16-31) and a 16-bit secondary region (bits 0-15.) A bitvector asserts a match in all position(s) of altarray corresponding to bitvector position(s) set to 1.

Probe elements p1 through pn reference a consecutive region of a memory object into which the probe data must be entered before calling probealtarray().

Probe bitvectors are limited to 32 bits and therefore dual bitvectors are limited to 16 bit positions. Hence the number of positions we are able to keep track of in altarray is also 16. The reason for the 32-bit restriction is to maintain compatibility with arc values, which often will be the carriers of data used to construct bitvector probes.

6.2.3. Return Value

The default (un-boosted) score returned when all probe elements exist in the array is 1.0. If one or more probe elements do not exist in array 0.0 is returned.

If probe elements match the array in order, starting from the first array element (a prefix match) the return score is multiplied by prefix_boost.

If any element match occurs because of a match in the secondary region (due to no match in primary region) the return score is multiplied by alt_deboost.

6.2.4. Remarks

Probe elements p1 through pn are evaluated against values in altarray, in order. Each time a match is found it is "consumed", i.e. if a probe element matched position q in altarray no subsequent probe element can match position q in altarray.

Probe elements encoded as dual-value integers are compared against values in altarray by matching primary regions first, then secondary regions if no match in primary regions. As long as ordered (prefix) matches can continue to be made, an element is declared a match if either primary or secondary regions match. If ordered matching fails, unordered matching is attempted and in this case all possibilities for primary region matches are considered before any secondary region matches are considered.

Probe elements encoded as dual bitvectors are used to assert matches in altarray at the position(s) corresponding to 1s in the bitvectors. When a bitvector probe pk is matched against a value in altarray, the position q of the least significant 1 in the bitvector that also corresponds to unmatched position q in altarray (if one exists) is marked as matched, i.e. position q cannot be matched by any subsequent probe. The primary bit-region of pk is processed first, then if no match the secondary bit-region of pk is processed. Note that bitvector probe pk will only consume one position q in altarray even if pk has multiple bits set to 1.

6.2.5. Example

# Create a vertex and assign a multi-valued
# property of dual-value integers
V = graph.NewVertex( "V" )
V['data'] = [ (1111 << 32) + 555, (4444 << 32) + 444, (3333 << 32) + 1000, (7777 << 32) + 555 ]

# Create the probe
probe = graph.Memory( 32 )
probe[1] = (1111 << 32) + 555   # primary match (pos 0)
probe[2] = (9999 << 32) + 1000  # secondary match (pos 2)
probe[3] = [ 0x00000009 ]       # secondary match (pos 0 and 3)

# Execute search
graph.Evaluate( "probealtarray( 1 , 3, vertex['data'], 100.0, 0.5 )", head=V, memory=probe )  # -> 0.5

# Explanation:
# probe[1] has a primary match for 1111 in pos 0
# probe[2] has a secondary match for 1000 in pos 2
# probe[3] is a bitvector and asserts secondary matches
#          in positions 0 and 3 (0x9 = binary 1001). Position 0
#          was consumed by probe[1] but position 3 was not
#          yet consumed and therefore a match occurs.
# We do not have a prefix match, and we are falling back
# on secondary matches, hence the returned score is
# 1.0 * 0.5 = 0.5.

6.3. synarc.decay() and synarc.xdecay()

Compute age-dependent arc value.

Linear decay synarc.decay()

\( N_t = N_0 - r \cdot t \)

Exponential decay synarc.xdecay()

\( N_t = N_0 e^{-\lambda t} \)

6.3.1. Syntax

synarc.decay( relationship[, rate] ) # -> decayed_value Nt
synarc.xdecay( relationship[, rate] ) # -> decayed_value Nt

6.3.2. Parameters

Parameter Type Default Description

relationship

string or integer

Relationship type given as string or as integer enumeration. To enable computation of decayed value Nt the relationship must be represented by a multiple arc comprising a creation (M_TMC) and/or modification time (M_TMM), an initial value N0 (floating point modifier(s)), and an expiration time (M_TMX), all sharing the same relationship type.

rate

real

\( \displaystyle \frac{1}{t_{end} - t_0} \)

Optional rate overrides rate derived from arc timestamps. Decay constants are always computed as:

\( r = rate \cdot N_0 \)

\( \lambda = rate \cdot ln \left( 10^4 \right) \)

By default linear decay produces \( N_t = 0 \) at \( t_1 = t_{end} \) and exponential decay produces \( N_t = \frac{N_0}{10000} \) at \( t_1 = t_{end} \).

6.3.3. Return Value

This function returns either null or a real value:

null

Evaluation of the expression containing synarc.decay() or synarc.xdecay() occurs for each traversed arc during neighborhood traversal, and for all such evaluations this function returns null.

Nt

Post-traversal evaluation of the expression takes place each time all arcs between two vertices have been processed, and at this point a real value representing decayed value Nt is returned.

6.3.4. Remarks

Arc decay functions are special in that they require multiple evaluations to compute a result. Arc components are first captured during traversal of the multiple arc, and finally the decayed value Nt is computed using those components when the expression is invoked one additional time post-traversal.

6.3.4.1. Capture arc components

During processing of arcs the arc decay functions will capture arc information for arcs whose relationship matches the rel argument. The arc’s value is captured and interpreted according to the modifier.

t0

(A) -[ rel, M_TMC, tc ]-> (B)
(A) -[ rel, M_TMM, tm ]-> (B)

When a decay function is evaluated at time t1 the creation (or modification) time t0 provides a reference for computing elapsed time t required by the decay formulas. If both M_TMC and M_TMM arcs exist the value from the M_TMM arc takes precedence.

\( t_0 = \begin{cases} t_m & \quad \text{if M_TMM arc exists} \\ t_c & \quad \text{if M_TMC arc exists and M_TMM arc does not exist} \end {cases} \)

If neither M_TMC nor M_TMM arc exists t0 takes on a default value:

\( t_0 = \begin{cases} t_{end} - \frac{1}{rate} & \quad \text{if rate > 0 and M_TMX arc exists} \\ graph.t_0 & \quad \text{if rate = 0 or M_TMX arc does not exist} \end {cases} \)

where \( graph.t_0 \) is the graph inception time.

tend

(A) -[ rel, M_TMX, tx ]-> (B)

The time t1 at which the arc’s decayed value reaches its minimum is tend, which is provided by an arc with the M_TMX modifier.

\( t_{end} = t_x \)

If no arc with modifier M_TMX exists tend takes on a default value:

\( t_{end} = \begin{cases} t_0 + \frac{1}{rate} & \quad \text{if } rate > 0 \text{ and } t_0 > graph.t_0 \\ \to \infty & \quad \text{if } rate = 0 \text{ or } t_0 \leq graph.t_0 \end{cases} \)

N0

(A) -[ rel, M_FLT, vi ]-> (B)
(A) -[ rel, M_ACC, vi ]-> (B)
(A) -[ rel, M_SIM, vi ]-> (B)
(A) -[ rel, M_DIST, vi ]-> (B)

The arc’s initial value N0 is derived as the sum of all arcs with a floating point modifier. Let n be the number of such arcs, then:

\( N_0 = \begin{cases} \displaystyle \sum_{i=1}^n{v_i} & \quad \text{if } n > 0 \\ 1 & \quad \text{if } n = 0 \text{ and } rate > 0 \\ 0 & \quad \text{if } n = 0 \text{ and } rate = 0 \end{cases}\)

Arcs with relationships other than rel, or with modifiers other than those with timestamp or floating point values are ignored by the arc decay function. The ignored non-floating point modifiers are M_STAT, M_LSH, M_INT, M_UINT, and M_CNT.

The value returned by synarc.decay() and synarc.xdecay() is always null when the expression is evaluated as part of arc traversal.

6.3.4.2. Post-traversal evaluation

Arc decay functions are available for use in adjacency filters and traversal filters, i.e. filters involving processing of arcs. Normally such filters evaluate once for each arc. However, when an expression contains one or more calls to an arc decay function the expression is invoked one additional time for each head vertex following completion of arc processing for that head vertex. Specifically, if a arcs are processed for h head vertices the expression is invoked a + h times, instead of the normal a times for expressions without arc decay functions.

Beware of the extra evaluation per vertex pair since this will have side-effects for other functions within the same expression such as count(), inc(), rwrite(), and many others.
6.3.4.3. Synthetic arc and return value

The arc processed by the expression at the final invocation is a synthetic arc that triggers different behavior for functions like synarc.decay() and synarc.xdecay(). The behavior triggered is to compute and return the decayed value Nt based on the current time t1 and arc components captured during processing of arcs.

Expressions should check for next.arc.issyn == true or include a type-check such as isreal() on the value returned by synarc.decay() and synarc.xdecay() if the value is to be used as part of a larger expression.

6.3.4.4. Decay Formula Summary
Elapse time t

The relative elapse time t is the difference between the expression invocation time t1 and the arc’s initial time t0.

\( t = t_1 - t_0 \)

Decay rate

If rate is not explicitly given, it is derived from the arc’s lifespan:

\( rate = \displaystyle \frac{1}{t_{end} - t_0} \)

Initial value

The arc’s initial value N0 is computed as:

\( N_0 = \displaystyle \sum_{i=1}^n{ v_i } \)
where n is the number of arc values v with a floating point modifier.

If no arcs exist with a floating point modifier (n = 0) the initial value defaults to:

\( N_0 = \begin{cases} 1 & \quad \text{if } rate > 0 \\ 0 & \quad \text {if } rate = 0 \end{cases}\)

Linear decay function synarc.decay( rel[, rate] )

The arc value at elapse time t is computed as:

\( N_t = N_0 - r \cdot t \)

\( r = \begin{cases} \frac{N_0}{t_{end} - t_0} & \quad \text{if no rate parameter} \\ rate \cdot N_0 & \quad \text{if rate parameter specified} \end{cases} \)

Exponential decay function synarc.xdecay( rel[, rate] )

The arc value at elapse time t is computed as:

\( N_t = N_0 e^{-\lambda t} \)

\( \lambda = \begin{cases} \frac{ln\left( 10^4 \right)}{t_{end} - t_0} & \quad \text{if no rate parameter} \\ rate \cdot \ln\left( 10^4 \right) & \quad \text{if rate parameter specified} \end{cases} \)

When exponential decay constant λ is derived from the arc’s lifespan it is chosen such that Nt decays to 0.01% of the arc’s initial value N0 at t1 = tend. This also implies a decay to 10% of initial value 1/4 through the lifespan, 1% of initial value 1/2 through the lifespan, and 0.1% of initial value 3/4 through the lifespan.

6.3.5. Returning decayed arc value in search result

Search results include arcs that are collected during graph traversal. Arcs are collected into the result set in accordance with the query collect parameter or the vertex condition 'traverse': { 'collect': …​ } clause. Values computed in evaluator expressions can also be collected but must be done using collect() or collectif() functions.

When collecting search results using evaluator expressions it is often desirable to collect using the C_SCAN mode. This mode disables automatic collection of traversed arcs and ensures traversal of the entire neighborhood.

To collect an arc with a decayed value we pass the result of synarc.decay() or synarc.xdecay() to collect().

6.3.5.1. Example: Collect decayed arc value
collect( synarc.decay( 'score' ) )

The function collect( value ) works by adding the currently traversed arc to the search result with the arc’s value overridden by value, which in the above example is the decayed value. Note that collect( value ) does nothing unless value is numeric and therefore ignores the null return value from synarc.decay() during traversal (i.e. when arc components needed to compute the decayed value are captured.) Only the post-traversal evaluation step results in a numeric value and this is when the arc is collected.

The arc collected this way will appear in the result as:

-[ 'score', M_FLT, Nt ]->

6.3.6. Complete example

from pyvgx import *

# Build a small graph
g = Graph( "decaytest" )
g.Connect( "A", ("score", M_FLT, 100.0), "B", lifespan=3600 )
g.Connect( "A", ("score", M_FLT, 50.0),  "C", lifespan=3600 )
g.Connect( "A", ("score", M_FLT, 25.0),  "D", lifespan=86400 )

# Wait a few seconds...

# Query with LINEAR DECAY
g.Neighborhood( id      = "A",
                collect = C_SCAN,
                fields  = F_AARC,
                filter  = "collect( synarc.decay( 'score' ) )"
)
# [
#   ( A )-[ score <M_FLT> 99.8160 ]->( B ),
#   ( A )-[ score <M_FLT> 49.9080 ]->( C ),
#   ( A )-[ score <M_FLT> 24.9981 ]->( D )
# ]

# Wait some more...

# Query with EXPONENTIAL DECAY
g.Neighborhood( id      = "A",
                collect = C_SCAN,
                fields  = F_AARC,
                filter  = "collect( synarc.xdecay( 'score' ) )"
)
# [
#   ( A )-[ score <M_FLT> 43.6150 ]->( B ),
#   ( A )-[ score <M_FLT> 21.8075 ]->( C ),
#   ( A )-[ score <M_FLT> 24.1504 ]->( D )
# ]

# Query with EXPONENTIAL DECAY and override decay rate to emulate
# 15 minute lifespan of all arcs.
g.Neighborhood( id      = "A",
                collect = C_SCAN,
                fields  = F_AARC,
                filter  = "collect( synarc.xdecay( 'score', 1/900 ) )"
)
# [
#   ( A )-[ score <M_FLT> 0.0552536 ]->( B ),
#   ( A )-[ score <M_FLT> 0.0276268 ]->( C ),
#   ( A )-[ score <M_FLT> 0.0138134 ]->( D )
# ]

PYVGX