Link Search Menu Expand Document
Table of contents
  1. General Structure
  2. Gentle Intro Example
  3. Procedures Programming
  4. Query Language
    1. Initializing Tables
    2. TRANSIT
      1. Returns
      2. Description
      3. hc_plan table

General Structure

HyperC is composed of a core library hyperc, the relational data interpretation layer hyper-etable, database interoperability hyperc-psql-proxy and a nice Python integration module called ordered

HyperC has a layered approach (see architecture) centered around Python and PDDL.

You define the data in objects of types (or rows of tables in database terminology), define procedures or actions that can modify the data, and the goal state to reach. HyperC will then select the correct procedures to run to get to that state.

You can choose to write procedures directly as pure Python files and run with ordered or you can work with a pre-packaged PostgreSQL interface that offers built-in data management, procedure storage and planning in one package. The ability to write and test procedures in Python may be helpful to construct and debug larger models.

Gentle Intro Example

To understand goal-oriented programming, let’s look at the simplest example in Python:

from hyperc import solve

class Numbers:
    i: int

n0 = Numbers()
n0.i = 0

def inc(n: Numbers):
    n.i += 1

def goal(n: Numbers):
    assert n.i == 3

solve(goal)

print(n0.i)  # => 3

This code defines the end goal to solve for a state where some object of type Numbers will have <object Numbers>.i == 3. Given is the function that takes object and increments the property i and one object with a star value .i=0. The resulting plan when HyperC solves this is:

inc(n0)
inc(n0)
inc(n0)

The same task in the database will look like this:

CREATE TABLE numbers ( id integer PRIMARY KEY, i integer );
INSERT INTO numbers VALUES (0, 0);
CREATE PROCEDURE inc(n numbers) LANGUAGE 'hyperc' AS $$
n.I += 1
$$;
TRANSIT UPDATE numbers SET i = 3;

the TRANSIT UPDATE command tells HyperC to reach the state of the table numbers that would have been set by the proposed UPDATE.

Note that we always have to have some KEY column in the table that will not be modified, so we had to create an additional id column. Also as databases don’t distinguish column name case, uppercase column names is a requirement.

You will get the output

step_num   |  proc_name | op_type
-----------+------------+---------
         0 |  inc       | STEP
         1 |  inc       | STEP
         2 |  inc       | STEP
        -1 |            | UPDATE

and as expected,

SELECT * FROM numbers;
 id | i 
----+---
  0 | 3
(1 row)

we can try to roll back using TRANSIT UPDATE:

TRANSIT UPDATE numbers SET i = 0;
ERROR:  hyperc.exceptions.SchedulingError: Obtained proof that task has no solution

which obviously fails as there is no set of procedures that could be applied to set the column i to 0.

Sending a normal UPDATE will fix the table back to original state:

UPDATE numbers SET i = 0;
UPDATE 1
SELECT * FROM numbers;
 id | i 
----+---
  0 | 0
(1 row)

Procedures Programming

HyperC accepts writing procedures in a subset of Python language. By writing the procedures, you define the allowed transitions within the database. We refer to a complete set of procedures as the “business schema”.

Let’s use this code as an example:

def move_truck(truck: Trucks, waypoint_pair: Waypoints):
    assert truck.LOCATION == waypoint_pair.FROM_LOCATION
    truck.LOCATION = waypoint_pair.TO_LOCATION
    truck.ODOMETER += waypoint_pair.POINTS_DISTANCE

When adding the code to the database, the procedure can be created with:

CREATE PROCEDURE move_truck(t trucks, l location_adjacency)
LANGUAGE 'hyperc'
AS $BODY$
assert truck.LOCATION == waypoint_pair.FROM_LOCATION
truck.LOCATION = waypoint_pair.TO_LOCATION
truck.ODOMETER += waypoint_pair.POINTS_DISTANCE
$BODY$;

Which is almost exact equivalent aside from that it’s not indented.

The line with assert defines the condition when this function can be run - when objects truck and waypoint_pair are related in a way that LOCATION property of truck equals FROM_LOCATION of waypoint_pair. You can think of this constraint as a JOIN between tables Trucks and Waypoints but with only a single pair of them taken from the join when a function is executed.

The last two lines update the respective values of a row (object).

There is a limit of what can be done in HyperC’ Python dialect: only plain Python is supported with if/assert, integers, bools and strings.

Query Language

HyperCDB is based on PostgreSQL database v.14 and most functions of the database work as expected.

We extend SQL language with the TRANSIT * set of commands:

Initializing Tables

TRANSIT INIT

Prepares the database for planning function.

TRANSIT

[ EXPLAIN [ TO table_name1[.column], table_name2, ... ]] TRANSIT UPDATE table_name
   SET { column = { expression | DEFAULT } |
          ( column [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...]
    [ WHERE condition ]

Returns

A table with the plan.

Description

TRANSIT UPDATE initiates transition to the state defined by UPDATE statement with familiar SQL syntax of UPDATE. It returns the table of the plan with unique plan_id that can be remembered and used to query hc_plan table to recall this plan at any later time.

EXPLAIN TRANSIT ... - initiates calculation of the plan, stores and outputs the plan table but does not do any actual updates to the state.

EXPLAIN TO *table_name*, ... TRANSIT ... - instructs the solver to only write down changes to tables (and possibly columns) specified after TO keyword.

Examples:

Calculate transition plan but only write down odometer reading, leaving truck at its original location:

EXPLAIN TO trucks.odometer TRANSIT UPDATE trucks SET location = 'Office';

hc_plan table

HyperCDB defines a special table hc_plan to incrementally store all plans with called procedure names and input/output parameters in JSONB objects.

The purpose of hc_plan table is to easily extract additional information from the plans like tracing the truck travel path, measuring fuel consumption, etc.

When TRANSIT command completes, it outputs the plan table back to the user connection. plan_id can be extracted and remembered by the client application to select this plan from later.