Tesseract 0.28.4
Loading...
Searching...
No Matches
Tesseract PropertyTree Polymorphic Types Example

Overview

This example demonstrates the polymorphic type system in Tesseract's PropertyTree, which enables schema validation for type hierarchies where fields accept a base type or any registered derived type.

Polymorphic types are useful for:

  • Type Inheritance: Accept base class or any subclass
  • Plugin Architectures: Accept base plugin type or derived implementations
  • Constraint Systems: Accept base constraint or specific constraint types
  • Extensibility: New derived types can be registered without schema changes
  • Dynamic Dispatch: Configuration data includes "type" field for validation selection

Key Concepts

  • Base Type: A registered type that other types can derive from
  • Derived Type: A type that extends the base type and is registered as such
  • Type Registration: Using TESSERACT_SCHEMA_REGISTER_DERIVED_TYPE macro
  • Type Field: A required field in configuration specifying the concrete type
  • Polymorphic Validation: Field marked with acceptsDerivedTypes() accepts any registered derived type
  • Type Safety: Validation checks if the type is valid in the hierarchy

Polymorphic Validation Workflow

The example demonstrates:

  1. Register Base Schema: Define the base type schema
  2. Register Derived Schemas: Define schemas for each derived type
  3. Register Inheritance: Tell registry which types derive from base
  4. Mark Field: Use acceptsDerivedTypes() on fields accepting polymorphic types
  5. Validate: System checks "type" field is valid and validates against corresponding schema

Example Code

// clang-format off
// Define base constraint schema with common fields
auto base_constraint_schema = PropertyTreeBuilder()
.attribute(TYPE, CONTAINER)
.doubleNum("weight").defaultVal(1.0).minimum(0.0).doc("Constraint weight in optimization").done()
.build();
// Define position constraint schema (extends base)
auto position_constraint_schema = PropertyTreeBuilder()
.attribute(TYPE, CONTAINER)
.doubleNum("weight").defaultVal(1.0).minimum(0.0).doc("Constraint weight in optimization").done()
.doubleNum("target_x").required().doc("Target position X coordinate").done()
.doubleNum("target_y").required().doc("Target position Y coordinate").done()
.doubleNum("target_z").required().doc("Target position Z coordinate").done()
.doubleNum("target_tolerance").defaultVal(0.01).minimum(0.0).doc("Position tolerance in meters").done()
.build();
// Define orientation constraint schema (extends base)
auto orientation_constraint_schema = PropertyTreeBuilder()
.attribute(TYPE, CONTAINER)
.doubleNum("weight").defaultVal(1.0).minimum(0.0).doc("Constraint weight in optimization").done()
.doubleNum("target_roll").required().doc("Target roll angle in radians").done()
.doubleNum("target_pitch").required().doc("Target pitch angle in radians").done()
.doubleNum("target_yaw").required().doc("Target yaw angle in radians").done()
.doubleNum("target_tolerance").defaultVal(0.1).minimum(0.0).doc("Orientation tolerance in radians").done()
.build();
//clang-format on
// Register all schemas
registry->registerSchema("Constraint", base_constraint_schema);
registry->registerSchema("PositionConstraint", position_constraint_schema);
registry->registerSchema("OrientationConstraint", orientation_constraint_schema);
// Register inheritance relationships
registry->registerDerivedType("Constraint", "PositionConstraint");
registry->registerDerivedType("Constraint", "OrientationConstraint");

First, register the type hierarchy. Base types and derived types each get schemas. Then use TESSERACT_SCHEMA_REGISTER_DERIVED_TYPE to establish relationships.

// Create a trajectory schema with a container of polymorphic constraints
auto trajectory_schema = PropertyTreeBuilder()
.attribute(TYPE, CONTAINER)
.string("name").required().doc("Name of this trajectory").done()
.container("constraint").doc("Task constraint (any constraint type can be used)")
.customType("constraint", "Constraint")
.acceptsDerivedTypes()
.validator(validateCustomType)
.done()
.build();

Mark sequence/map fields with acceptsDerivedTypes() to allow derived types. The validator will check each element's "type" field against the hierarchy.

YAML::Node valid_config;
valid_config["name"] = "approach_trajectory";
valid_config["constraint"]["type"] = "PositionConstraint";
valid_config["constraint"]["weight"] = 2.0;
valid_config["constraint"]["target_x"] = 1.0;
valid_config["constraint"]["target_y"] = 2.0;
valid_config["constraint"]["target_z"] = 3.0;
valid_config["constraint"]["target_tolerance"] = 0.005;
auto schema_copy = trajectory_schema;
schema_copy.mergeConfig(valid_config);
auto errors = schema_copy.validate();

During validation, the system checks if each element's type is registered as derived from (or equal to) the base type, then validates against that type's schema.

Comparing Polymorphism Approaches

PropertyTree offers two polymorphism mechanisms:

Feature Derived Types OneOf
Selection Method Type field (explicit naming) Required field match (implicit)
Type Discovery "type": "DerivedClassName" Keys present in config determine branch
Registration Runtime via macros (compile-time execution) Static in schema definition
Extensibility Very extensible - new types can be added Requires schema modification
Type Hierarchies Supports inheritance chains Not designed for hierarchies
Error Messages Clear type name in error Shows expected/actual keys
Typical Use Case Plugins, constraints, different algorithm implementations Different communication methods,

shape types |

Best Practices

  1. Always Include Type Field: Polymorphic fields need explicit type information
  2. Use Clear Type Names: Make type names match class names when possible
  3. Register Early: Use TESSERACT_SCHEMA_REGISTER_DERIVED_TYPE at module init time
  4. Document Type Hierarchy: Add doc attributes explaining what types are valid
  5. Provide Type Defaults: Consider if a default type makes sense for the field
  6. Validate Complete Config: Always call validate() and check all error messages
  7. Consider Schema Versioning: Plan for adding new derived types in the future
  8. Test All Types: Write validation tests covering each derived type variant

Running the Example

Execute the example:

./tesseract_common_polymorphic_property_tree_example

The example demonstrates:

  • Registering type hierarchies
  • Validating polymorphic sequences with multiple types
  • Error handling for invalid types
  • Accessing derived type instances
  • Schema inspection with metadata