Relationships

Sina Relationship objects are used as a way to describe the correlation between two Record instances (and/or Record inheritors, e.g. Run). A Relationship consists of three parts: a subject, an object, and a predicate. The subject and object must be IDs referring to valid instances of Records, while the predicate may be any string.

In describing the connection between objects, a Relationship is read as “<subject> <predicate> <object>”. For example, in the relationship “Alice knows Bob”, “Alice” is the subject, “knows” is the predicate, and “Bob” is the object. Below are some additional examples:

  • Task_22 contains Run_1024

  • msub_1_1 describes out_j_1_1

  • Carlos sends an email to Dani

  • local_task_12 runs before local_run_14

A Relationship should be described in the active voice. Using active voice in predicates is recommended to maintain a clear direction in the relationship. For example, instead of the passive construction “Dani is emailed by Carlos,” use the active form “Carlos emails Dani.”

Below is an example showcasing how to construct a Relationship programmatically. Here, we assemble a Relationship showing that “Task_22 contains Run_1024”:

// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and
// other Axom Project Developers. See the top-level LICENSE file for details.
//
// SPDX-License-Identifier: (BSD-3-Clause)

#include "axom/sina.hpp"
#include "axom/slic.hpp"

int main(void)
{
  // Initialize slic
  axom::slic::initialize();

  // Create IDs for both Task 22 and Run 1024
  axom::sina::ID task22 {"Task_22", axom::sina::IDType::Global};
  axom::sina::ID run1024 {"Run_1024", axom::sina::IDType::Global};

  // Create the relationship
  axom::sina::Relationship myRelationship {task22, "contains", run1024};

  // Compare the actual output to the expected JSON output, then print it to console
  std::string actualJsonString = myRelationship.toNode().to_json();
  std::string expectedJsonString = R"(
{
  "predicate": "contains",
  "subject": "Task_22",
  "object": "Run_1024"
})";
  SLIC_ASSERT_MSG(actualJsonString.compare(expectedJsonString) == 0,
                  "JSON output does not match expected structure.");
  std::cout << actualJsonString << std::endl;

  // Finalize slic
  axom::slic::finalize();
}

If executed, the above code will output:

{
    "predicate": "contains",
    "subject": "Task_22",
    "object": "Run_1024"
}

As with any other Sina ID, the subject or object may be either local (uniquely refer to one object in a Sina file) or global (uniquely refer to one object in a database). Local IDs are replaced with global ones upon ingestion; all Relationship instances referring to that local ID (as well as the Record possessing that ID) will be updated to use the same global ID.

Let’s add on to our previous example to demonstrate this:

// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and
// other Axom Project Developers. See the top-level LICENSE file for details.
//
// SPDX-License-Identifier: (BSD-3-Clause)

#include "axom/sina.hpp"

int main(void)
{
  // Create IDs for Task 22 and Run 1024
  axom::sina::ID task22 {"Task_22", axom::sina::IDType::Global};
  axom::sina::ID run1024 {"Run_1024", axom::sina::IDType::Global};

  // Create the relationship and print it out
  axom::sina::Relationship myRelationship {task22, "contains", run1024};
  std::cout << myRelationship.toNode().to_json() << std::endl;

  // Create a new ID with local scope and use it to create a Record and Relationship
  axom::sina::ID myLocalID {"my_local_run", axom::sina::IDType::Local};
  std::unique_ptr<axom::sina::Record> myRun {
    new axom::sina::Run {myLocalID, "My Sim Code", "1.2.3", "jdoe"}};
  axom::sina::Relationship myLocalRelationship {task22, "containts", myLocalID};
}

In the above code, the “my_local_run” ID would be replaced by a global ID on ingestion. If this new global ID was, for example, “5Aed-BCds-23G1”, then “my_local_run” would automatically be replaced by “5Aed-BCds-23G1” in both the Record and Relationship entries.