In order to use Graph Grammar user have to build node and rule vocabularies the instances of the NodeVocabulary and RuleVocabulary classes and fill them with nodes and rules for the graph. This tutorial provides the description of the creating nodes and rules process.
Before starting to build your vocabularies import all necessary libs standard and from the rostok package
import numpy as np
import matplotlib.pyplot as plt
#import networkx as nx
import pychrono as chrono
from rostok.graph_grammar.node import BlockWrapper, ROOT
from rostok.graph_grammar import node_vocabulary, rule_vocabulary
from rostok.block_builder.node_render import *
It is simpler to have a function to construct vocabularies (see rostok\app\rule_extension.py
), but here we would show how to set them step by step. Create and instance of the class NodeVocabulary
and add the imported node ROOT to your vocabulary, the ROOT node is special starting point that have to be in any vocabulary.
There are two types of nodes terminal and non-terminal. The non-terminal nodes describe a part of the robot in general without physical properties, for example node "J" represents joints. The nodes are the objects of the class Node
and terminal nodes actually have only one attribute - label. So one should plan how many structural nodes is needed in the rules. Here we have 14 nodes.
# Creating instance of the NodeVocabulary and adding ROOT to it
node_vocab = node_vocabulary.NodeVocabulary()
node_vocab.add_node(ROOT)
node_vocab.create_node("J") # joints
node_vocab.create_node("L") # links
node_vocab.create_node("F") # palm
node_vocab.create_node("EM") # fingertip
# nodes that represents coordinates of and rotations of the finger bases in respect to the palm
node_vocab.create_node("SML")
node_vocab.create_node("SMR")
node_vocab.create_node("SMRP")
node_vocab.create_node("SMRPA")
node_vocab.create_node("SMLP")
node_vocab.create_node("SMLPA")
node_vocab.create_node("SMRM")
node_vocab.create_node("SMRMA")
node_vocab.create_node("SMLM")
node_vocab.create_node("SMLMA")
The second type of nodes are terminal nodes - they represent the physical properties of the nodes in the robot. The physical properties of a node are defined through an instance of the BlockWrapper
class. To construct the BlockWrapper
object one have to chose the type of the node by choosing one of the classes from node_render module and set the required for that class parameters.
Here for a joint node we choose ChronoRevolveJoint
set the rotation around Z axis and set the type of the control - TORQUE.
type_of_input = ChronoRevolveJoint.InputType.TORQUE
revolve1 = BlockWrapper(ChronoRevolveJoint, ChronoRevolveJoint.Axis.Z, type_of_input)
node_vocab.create_node(label="J1", is_terminal=True, block_wrapper=revolve1)
For each non-terminal nodes one can have several terminal nodes - several details with specific physical properties that have the same function and hence the same non-terminal node. Here we have three palms with different sizes specified with width. The FlatChronoBody
represents the fixed part of the mechanism and has the form of the box. Here we only set the size of the box and other parameters remain default. The total list of parameters can be seen in the class documentation.
width = [0.25, 0.35, 0.5]
flat = list(map(lambda x: BlockWrapper(FlatChronoBody, width=x, length=0.05, depth=0.8), width))
node_vocab.create_node(label="F1", is_terminal=True, block_wrapper=flat[0])
node_vocab.create_node(label="F2", is_terminal=True, block_wrapper=flat[1])
node_vocab.create_node(label="F3", is_terminal=True, block_wrapper=flat[2])
Next are the links that represents the parts of the fingers. Here we create three different links specified by lengths using class LinkChronoBody
. It also has more parameters that can be seen in class documentation.
length_link = [0.4, 0.6, 0.8]
link = list(map(lambda x: BlockWrapper(LinkChronoBody, length=x),length_link))
node_vocab.create_node(label="L1", is_terminal=True, block_wrapper=link[0])
node_vocab.create_node(label="L2", is_terminal=True, block_wrapper=link[1])
node_vocab.create_node(label="L3", is_terminal=True, block_wrapper=link[2])
We also add two different fingertips that would end the fingers of the grasping mechanism, setting them with two sizes. Other parameters see in documentation.
u1 = BlockWrapper(MountChronoBody, width=0.1, length=0.05)
u2 = BlockWrapper(MountChronoBody, width=0.2, length=0.1)
node_vocab.create_node(label="U1", is_terminal=True, block_wrapper=u1)
node_vocab.create_node(label="U2", is_terminal=True, block_wrapper=u2)
Before we had nodes that represented the physical bodies. Here we consider the special case - nodes that represent the coordinates of the base of a finger on the palm and its angle. In that set of nodes it is the most difficult part. First we create function that transforms angle in degrees into the rotation that can be set for Pychrono
object.
def rotation(alpha):
quat_Y_ang_alpha = chrono.Q_from_AngY(np.deg2rad(alpha))
return [quat_Y_ang_alpha.e0, quat_Y_ang_alpha.e1, quat_Y_ang_alpha.e2,quat_Y_ang_alpha.e3]
For each palm we create several movements using FrameTransform
object
alpha = 30
MOVE_TO_RIGHT_SIDE = map(lambda x: FrameTransform([x, 0, 0],[0,0,1,0]),
width)
MOVE_TO_RIGHT_SIDE_PLUS = map(lambda x: FrameTransform([x, 0, +0.3],[0,0,1,0]),
width)
MOVE_TO_RIGHT_SIDE_PLUS_ANGLE = map(lambda x: FrameTransform([x, 0, +0.3], rotation(180 - alpha)),
width)
MOVE_TO_RIGHT_SIDE_MINUS = map(lambda x: FrameTransform([x, 0, -0.3],[0,0,1,0]),
width)
MOVE_TO_RIGHT_SIDE_MINUS_ANGLE = map(lambda x: FrameTransform([x, 0, -0.3],rotation(180 + alpha)),
width)
MOVE_TO_LEFT_SIDE = map(lambda x: FrameTransform([-x, 0, 0],[1,0,0,0]),
width)
MOVE_TO_LEFT_SIDE_PLUS = map(lambda x: FrameTransform([-x, 0, +0.3],[1,0,0,0]),
width)
MOVE_TO_LEFT_SIDE_PLUS_ANGLE = map(lambda x: FrameTransform([-x, 0, +0.3],rotation(alpha)),
width)
MOVE_TO_LEFT_SIDE_MINUS = map(lambda x: FrameTransform([-x, 0, -0.3],[1,0,0,0]),
width)
MOVE_TO_LEFT_SIDE_MINUS_ANGLE = map(lambda x: FrameTransform([-x, 0, -0.3],rotation(-alpha)),
width)
For each movement we also create a BlockWrapper
object
transform_to_right_mount = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_RIGHT_SIDE))
transform_to_right_mount_plus = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_RIGHT_SIDE_PLUS))
transform_to_right_mount_plus_angle = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_RIGHT_SIDE_PLUS_ANGLE))
transform_to_right_mount_minus = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_RIGHT_SIDE_MINUS))
transform_to_right_mount_minus_angle = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_RIGHT_SIDE_MINUS_ANGLE))
transform_to_left_mount = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_LEFT_SIDE))
transform_to_left_mount_plus = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_LEFT_SIDE_PLUS))
transform_to_left_mount_plus_angle = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_LEFT_SIDE_PLUS_ANGLE))
transform_to_left_mount_minus = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_LEFT_SIDE_MINUS))
transform_to_left_mount_minus_angle = list(map(lambda x: BlockWrapper(ChronoTransform, x),
MOVE_TO_LEFT_SIDE_MINUS_ANGLE))
And finally create all terminal nodes
node_vocab.create_node(label="TR1", is_terminal=True, block_wrapper=transform_to_right_mount[0])
node_vocab.create_node(label="TR2", is_terminal=True, block_wrapper=transform_to_right_mount[1])
node_vocab.create_node(label="TR3", is_terminal=True, block_wrapper=transform_to_right_mount[2])
node_vocab.create_node(label="TRP1", is_terminal=True, block_wrapper=transform_to_right_mount_plus[0])
node_vocab.create_node(label="TRP2", is_terminal=True, block_wrapper=transform_to_right_mount_plus[1])
node_vocab.create_node(label="TRP3", is_terminal=True, block_wrapper=transform_to_right_mount_plus[2])
node_vocab.create_node(label="TRPA1", is_terminal=True, block_wrapper=transform_to_right_mount_plus_angle[0])
node_vocab.create_node(label="TRPA2", is_terminal=True, block_wrapper=transform_to_right_mount_plus_angle[1])
node_vocab.create_node(label="TRPA3", is_terminal=True, block_wrapper=transform_to_right_mount_plus_angle[2])
node_vocab.create_node(label="TRM1", is_terminal=True, block_wrapper=transform_to_right_mount_minus[0])
node_vocab.create_node(label="TRM2", is_terminal=True, block_wrapper=transform_to_right_mount_minus[1])
node_vocab.create_node(label="TRM3", is_terminal=True, block_wrapper=transform_to_right_mount_minus[2])
node_vocab.create_node(label="TRMA1", is_terminal=True, block_wrapper=transform_to_right_mount_minus_angle[0])
node_vocab.create_node(label="TRMA2", is_terminal=True, block_wrapper=transform_to_right_mount_minus_angle[1])
node_vocab.create_node(label="TRMA3", is_terminal=True, block_wrapper=transform_to_right_mount_minus_angle[2])
node_vocab.create_node(label="TL1", is_terminal=True, block_wrapper=transform_to_left_mount[0])
node_vocab.create_node(label="TL2", is_terminal=True, block_wrapper=transform_to_left_mount[1])
node_vocab.create_node(label="TL3", is_terminal=True, block_wrapper=transform_to_left_mount[2])
node_vocab.create_node(label="TLP1", is_terminal=True, block_wrapper=transform_to_left_mount_plus[0])
node_vocab.create_node(label="TLP2", is_terminal=True, block_wrapper=transform_to_left_mount_plus[1])
node_vocab.create_node(label="TLP3", is_terminal=True, block_wrapper=transform_to_left_mount_plus[2])
node_vocab.create_node(label="TLPA1", is_terminal=True, block_wrapper=transform_to_left_mount_plus_angle[0])
node_vocab.create_node(label="TLPA2", is_terminal=True, block_wrapper=transform_to_left_mount_plus_angle[1])
node_vocab.create_node(label="TLPA3", is_terminal=True, block_wrapper=transform_to_left_mount_plus_angle[2])
node_vocab.create_node(label="TLM1", is_terminal=True, block_wrapper=transform_to_left_mount_minus[0])
node_vocab.create_node(label="TLM2", is_terminal=True, block_wrapper=transform_to_left_mount_minus[1])
node_vocab.create_node(label="TLM3", is_terminal=True, block_wrapper=transform_to_left_mount_minus[2])
node_vocab.create_node(label="TLMA1", is_terminal=True, block_wrapper=transform_to_left_mount_minus_angle[0])
node_vocab.create_node(label="TLMA2", is_terminal=True, block_wrapper=transform_to_left_mount_minus_angle[1])
node_vocab.create_node(label="TLMA3", is_terminal=True, block_wrapper=transform_to_left_mount_minus_angle[2])
Now all nodes are set and we can move to the creation of rules. At first create an instance of the RuleVocabulary
class. Terminal rules are straightforward - they should transform non-terminal nodes into all their terminal/physical representations
rule_vocab = rule_vocabulary.RuleVocabulary(node_vocab)
rule_vocab.create_rule("TerminalFlat1", ["F"], ["F1"], 0 , 0)
rule_vocab.create_rule("TerminalFlat2", ["F"], ["F2"], 0 , 0)
rule_vocab.create_rule("TerminalFlat3", ["F"], ["F3"], 0 , 0)
rule_vocab.create_rule("TerminalL1", ["L"], ["L1"], 0 , 0)
rule_vocab.create_rule("TerminalL2", ["L"], ["L2"], 0 , 0)
rule_vocab.create_rule("TerminalL3", ["L"], ["L3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRight1", ["SMR"], ["TR1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRight2", ["SMR"], ["TR2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRight3", ["SMR"], ["TR3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightPlus1", ["SMRP"], ["TRP1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightPlus2", ["SMRP"], ["TRP2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightPlus3", ["SMRP"], ["TRP3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightPlusAngle1", ["SMRPA"], ["TRPA1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightPlusAngle2", ["SMRPA"], ["TRPA2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightPlusAngle3", ["SMRPA"], ["TRPA3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightMinus1", ["SMRM"], ["TRM1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightMinus2", ["SMRM"], ["TRM2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightMinus3", ["SMRM"], ["TRM3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightMinusAngle1", ["SMRMA"], ["TRMA1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightMinusAngle2", ["SMRMA"], ["TRMA2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformRightMinusAngle3", ["SMRMA"], ["TRMA3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeft1", ["SML"], ["TL1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeft2", ["SML"], ["TL2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeft3", ["SML"], ["TL3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftPlus1", ["SMLP"], ["TLP1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftPlus2", ["SMLP"], ["TLP2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftPlus3", ["SMLP"], ["TLP3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftPlusAngle1", ["SMLPA"], ["TLPA1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftPlusAngle2", ["SMLPA"], ["TLPA2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftPlusAngle3", ["SMLPA"], ["TLPA3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftMinus1", ["SMLM"], ["TLM1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftMinus2", ["SMLM"], ["TLM2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftMinus3", ["SMLM"], ["TLM3"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftMinusAngle1", ["SMLMA"], ["TLMA1"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftMinusAngle2", ["SMLMA"], ["TLMA2"], 0 , 0)
rule_vocab.create_rule("TerminalTransformLeftMinusAngle3", ["SMLMA"], ["TLMA3"], 0 , 0)
rule_vocab.create_rule("TerminalEndLimb1", ["EM"], ["U1"], 0 , 0)
rule_vocab.create_rule("TerminalEndLimb2", ["EM"], ["U2"], 0 , 0)
rule_vocab.create_rule("TerminalJoint", ["J"], ["J1"], 0 , 0)
The most important part of the generative design is the set of non-terminal rules that changes the morphology of the mechanism. Each rule must transform a non-terminal node of the graph into a small graph of non-terminal nodes. It should also specify how to link the in and out edges of the transforming node. The rule is added to a rule_vocab
using create_rule
function of a RuleVocabulary class. The rule must have a unique name, the arguments are:
Here we create 7 different starting rules that transform ROOT into the initial mechanisms with different number of finger bases placed in different positions. And 1 rule to increase the amount of links and joints in a finger
rule_vocab.create_rule("InitMechanism_2", ["ROOT"], ["F", "SML", "SMR","EM","EM"], 0 , 0,[(0,1),(0,2),(1,3),(2,4)])
rule_vocab.create_rule("InitMechanism_3_R", ["ROOT"], ["F", "SML", "SMRP","SMRM","EM","EM","EM"], 0 , 0,[(0,1),(0,2),(0,3),(1,4),(2,5),(3,6)])
rule_vocab.create_rule("InitMechanism_3_R_A", ["ROOT"], ["F", "SML", "SMRPA","SMRMA","EM","EM","EM"], 0 , 0,[(0,1),(0,2),(0,3),(1,4),(2,5),(3,6)])
rule_vocab.create_rule("InitMechanism_3_L", ["ROOT"], ["F", "SMLP","SMLM", "SMR","EM","EM","EM"], 0 , 0, [(0,1),(0,2),(0,3),(1,4),(2,5),(3,6)])
rule_vocab.create_rule("InitMechanism_3_L_A", ["ROOT"], ["F", "SMLPA","SMLMA", "SMR","EM","EM","EM"], 0 , 0, [(0,1),(0,2),(0,3),(1,4),(2,5),(3,6)])
rule_vocab.create_rule("InitMechanism_4", ["ROOT"], ["F", "SMLP","SMLM", "SMRP","SMRM","EM","EM","EM","EM"], 0 , 0, [(0,1),(0,2),(0,3),(0,4),(1,5),(2,6),(3,7),(4,8)])
rule_vocab.create_rule("InitMechanism_4_A", ["ROOT"], ["F", "SMLPA","SMLMA", "SMRPA","SMRMA","EM","EM","EM","EM"], 0 , 0, [(0,1),(0,2),(0,3),(0,4),(1,5),(2,6),(3,7),(4,8)])
rule_vocab.create_rule("FingerUpper", ["EM"], ["J", "L","EM"], 0 , 2, [(0,1),(1, 2)])
Finally, one must set the categories of the terminal nodes
That categories are currently required for reward calculation and the list of node features will be used in other modules.
list_J = node_vocab.get_list_of_nodes(["J1"])
list_RM = node_vocab.get_list_of_nodes(["TR1", "TR2", "TR3","TRP1", "TRP2", "TRP3","TRM1", "TRM2", "TRM3", "TRPA1", "TRPA2", "TRPA3","TRMA1", "TRMA2", "TRMA3"])
list_LM = node_vocab.get_list_of_nodes(["TL1", "TL2", "TL3","TLP1", "TLP2", "TLP3","TLM1", "TLM2", "TLM3", "TLPA1", "TLPA2", "TLPA3","TLMA1", "TLMA2", "TLMA3"] )
list_B = node_vocab.get_list_of_nodes(["L1", "L2", "L3", "F1", "F2", "F3", "U1", "U2"])
# Required for criteria calc
node_features = [list_B, list_J, list_LM, list_RM]