Records

Sina Record objects are used to represent the data to be stored for your study. Some examples for the natural scope of Record objects include things like:

  • a single run of an application

  • a Maestro step

  • a cluster of runs that has some metadata attached to the cluster (this Record might have a “contains” Relationship for all the runs within it)

Each Record must have an ID and a type that you define. Additionally, Each Record can have a list of File objects and a list of Datum objects.

Datums

Sina Datum objects help track the value and (optionally) tags and/or units of a value associated with a Record. In the Sina schema, a Datum always belongs to a Record or one of Record’s inheriting types.

Some examples of potential Datum values would be:

  • a scalar

  • a piece of metadata

  • an input parameter

The value of a Datum may be a string, a double, an array of strings, or an array of doubles.

Below showcases an example of creating an instance of Datum with an array of strings and adding it to a Record:

// 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 the record
  axom::sina::ID myID {"my_record", axom::sina::IDType::Local};
  std::unique_ptr<axom::sina::Record> myRecord {new axom::sina::Record {myID, "my_type"}};

  // Create the datum with an array of strings
  std::vector<std::string> myTags {"input"};
  axom::sina::Datum myDatum {myTags};

  // Add the datum to the record
  myRecord->add("my_scalar", std::move(myDatum));

  // Compare the actual output to the expected JSON output, then print it to console
  std::string actualJsonString = myRecord->toNode().to_json();
  std::string expectedJsonString = R"(
{
  "data": 
  {
    "my_scalar": 
    {
      "value": 
      [
        "input"
      ]
    }
  },
  "type": "my_type",
  "local_id": "my_record"
})";
  SLIC_ASSERT_MSG(actualJsonString.compare(expectedJsonString) == 0,
                  "JSON output does not match expected structure.");
  std::cout << actualJsonString << std::endl;

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

Once executed, this code will output:

{
    "data":
    {
        "my_scalar":
        {
            "value":
            [
                "input"
            ]
        }
    },
    "type": "my_type",
    "local_id": "my_record"
}

Checking Datum Type

It’s possible to check the type of a Datum with the getType() method. Types are tracked in an enumeration called ValueType. The enumeration is as follows:

  • 0: string

  • 1: scalar

  • 2: array of strings

  • 3: array of scalars

Below is an example of this in action:

// 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"

using ValueTypeUnderlying = typename std::underlying_type<axom::sina::ValueType>::type;

void printType(axom::sina::Datum datum, std::string datumName, const std::string& errMsg)
{
  auto datumType = static_cast<ValueTypeUnderlying>(datum.getType());
  SLIC_ASSERT_MSG(static_cast<bool>(std::is_same<decltype(datumType), ValueTypeUnderlying>::value),
                  errMsg);
  AXOM_UNUSED_VAR(errMsg);

  std::cout << datumName << " type: " << datumType << std::endl;
}

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

  // Define 3 different datums
  axom::sina::Datum myDatum {12.34};
  std::string value = "foobar";
  axom::sina::Datum myOtherDatum {value};
  std::vector<double> scalars = {1, 2, 20.0};
  axom::sina::Datum myArrayDatum {scalars};

  // Prints 1, corresponding to Scalar
  printType(myDatum,
            "myDatum",
            "myDatumType did not match the expected type 'Scalar' (numerically "
            "represented as 1).");

  // Prints 0, corresponding to String
  printType(myOtherDatum,
            "myOtherDatum",
            "myDatumType did not match the expected type 'String' (numerically "
            "represented as 0).");

  // Prints 3, corresponding to ScalarArray
  printType(myArrayDatum,
            "myArrayDatum",
            "myArrayDatum did not match the expected type 'ScalarArray' "
            "(numerically represented as 3).");

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

Setting Units and Tags

For certain Datum instances it may be helpful to assign them units and/or tags. This can be accomplished with the setUnits() and setTags() methods respectively.

Below is an example of this functionality:

// 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)
{
  // Define 2 different datums
  axom::sina::Datum myDatum {12.34};
  std::vector<double> scalars = {1, 2, 20.0};
  axom::sina::Datum myArrayDatum {scalars};

  // Set the units for one datum and the tags for the other
  myDatum.setUnits("km/s");
  std::vector<std::string> tags = {"input", "core"};
  myArrayDatum.setTags(tags);
}

Viewing Datum From an existing Record

Sometimes it’s necessary to obtain the current Datum instances from an existing Record. To do this, you can utilize the Record object’s getData method. This method will return an unordered map of Datum instances.

Below is an example of this process:

// 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)
{
  // Define 3 different datums
  axom::sina::Datum myDatum {12.34};
  std::string value = "foobar";
  axom::sina::Datum myOtherDatum {value};
  std::vector<double> scalars = {1, 2, 20.0};
  axom::sina::Datum myArrayDatum {scalars};

  // Create a record to store the datum
  axom::sina::ID myID {"my_record", axom::sina::IDType::Local};
  std::unique_ptr<axom::sina::Record> myRecord {new axom::sina::Record {myID, "my_type"}};

  // Add the datum instances to the record
  myRecord->add("datum1", std::move(myDatum));
  myRecord->add("datum2", std::move(myOtherDatum));
  myRecord->add("datum3", std::move(myArrayDatum));

  // Query the datum from the record
  auto& data = myRecord->getData();

  // Print the keys and type of datum
  for(const auto& pair : data)
  {
    std::cout << pair.first << " is type: "
              << static_cast<std::underlying_type<axom::sina::ValueType>::type>(pair.second.getType())
              << std::endl;
  }
}

Executing this code will print out:

datum1 is type: 1
datum2 is type: 0
datum3 is type: 3

Which, we know from Checking Datum Type, signifies that datum1 is a scalar, datum2 is a string, and datum3 is an array of scalars.

Using this knowledge we can modify our code to show us the current datum values:

// 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();

  // Define 3 different datums
  double scalarValue = 12.34;
  axom::sina::Datum myDatum {scalarValue};
  std::string stringValue = "foobar";
  axom::sina::Datum myOtherDatum {stringValue};
  std::vector<double> scalarArrayValue = {1, 2, 20.0};
  axom::sina::Datum myArrayDatum {scalarArrayValue};

  // Create a record to store the datum
  axom::sina::ID myID {"my_record", axom::sina::IDType::Local};
  std::unique_ptr<axom::sina::Record> myRecord {new axom::sina::Record {myID, "my_type"}};

  // Add the datum instances to the record
  myRecord->add("datum1", std::move(myDatum));
  myRecord->add("datum2", std::move(myOtherDatum));
  myRecord->add("datum3", std::move(myArrayDatum));

  // Query the datum
  auto& data = myRecord->getData();

  // Print the datum values
  double datum1Val = data.at("datum1").getScalar();
  SLIC_ASSERT_MSG(datum1Val == scalarValue, "Data stored in record at 'datum1' is unexpected.");
  std::cout << "datum1: " << datum1Val << std::endl;

  std::string datum2Val = data.at("datum2").getValue();
  SLIC_ASSERT_MSG(datum2Val == stringValue, "Data stored in record at 'datum2' is unexpected.");
  std::cout << "datum2: " << datum2Val << std::endl;

  std::vector<double> datum3Val = data.at("datum3").getScalarArray();
  SLIC_ASSERT_MSG(datum3Val == scalarArrayValue, "Data stored in record at 'datum3' is unexpected.");
  std::cout << "datum3: ";
  for(const auto& value : datum3Val)
  {
    std::cout << value << " ";
  }
  std::cout << std::endl;

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

This will provide the following output:

datum1: 12.34
datum2: foobar
datum3: 1 2 20

Files

Sina File objects help track the location (URI) and mimetype of a file on the file system, plus any tags. In the Sina schema, a File always belongs to a Record or one of Record’s inheriting types.

Every File must have a URI, while mimetype and tags are optional.

Below is an example showcasing how to create a file and add it to a record:

// 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 2 different files
  axom::sina::File myFile {"/path/to/file.png"};
  myFile.setMimeType("image/png");
  axom::sina::File myOtherFile {"/path/to/other/file.txt"};
  myOtherFile.setTags({"these", "are", "tags"});

  // Create a record to store the files
  axom::sina::ID myID {"my_record", axom::sina::IDType::Local};
  std::unique_ptr<axom::sina::Record> myRecord {new axom::sina::Record {myID, "my_type"}};

  // Add the files to the record
  myRecord->add(myFile);
  myRecord->add(myOtherFile);

  // Compare the actual output to the expected JSON output, then print it to console
  std::string actualJsonString = myRecord->toNode().to_json();
  std::string expectedJsonString = R"(
{
  "type": "my_type",
  "local_id": "my_record",
  "files": 
  {
    "/path/to/other/file.txt": 
    {
      "tags": 
      [
        "these",
        "are",
        "tags"
      ]
    },
    "/path/to/file.png": 
    {
      "mimetype": "image/png"
    }
  }
})";
  SLIC_ASSERT_MSG(actualJsonString.compare(expectedJsonString) == 0,
                  "JSON output does not match expected structure.");
  std::cout << actualJsonString << std::endl;

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

This code will produce the following output:

{
    "type": "my_type",
    "local_id": "my_record",
    "files":
    {
        "/path/to/other/file.txt":
        {
            "tags":
            [
                "these",
                "are",
                "tags"
            ]
        },
        "/path/to/file.png":
        {
            "mimetype": "image/png"
        }
    }
}

Similarly, files can be removed from a Record with the remove() method:

// 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 2 different files
  axom::sina::File myFile {"/path/to/file.png"};
  myFile.setMimeType("image/png");
  axom::sina::File myOtherFile {"/path/to/other/file.txt"};
  myOtherFile.setTags({"these", "are", "tags"});

  // Create a record to store the files
  axom::sina::ID myID {"my_record", axom::sina::IDType::Local};
  std::unique_ptr<axom::sina::Record> myRecord {new axom::sina::Record {myID, "my_type"}};

  // Add the files to the record
  myRecord->add(myFile);
  myRecord->add(myOtherFile);

  // Remove a file from the record
  myRecord->remove(myFile);

  // Compare the actual output to the expected JSON output, then print it to console
  std::string actualJsonString = myRecord->toNode().to_json();
  std::string expectedJsonString = R"(
{
  "type": "my_type",
  "local_id": "my_record",
  "files": 
  {
    "/path/to/other/file.txt": 
    {
      "tags": 
      [
        "these",
        "are",
        "tags"
      ]
    }
  }
})";
  SLIC_ASSERT_MSG(actualJsonString.compare(expectedJsonString) == 0,
                  "JSON output does not match expected structure.");
  std::cout << actualJsonString << std::endl;

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

As we see from the output, the contents of myFile are no longer in the Record instance:

{
    "type": "my_type",
    "local_id": "my_record",
    "files":
    {
        "/path/to/other/file.txt":
        {
            "tags":
            [
                "these",
                "are",
                "tags"
            ]
        }
    }
}

Viewing Files From an Existing Record

Sometimes it’s necessary to view the current File instances that are stored in a Record. This can be accomplished by using the Record object’s getFiles() method which returns an unordered map of File instances.

Below is an expansion of the previous example where we query the Record instance for files:

// 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 2 different files
  axom::sina::File myFile {"/path/to/file.png"};
  myFile.setMimeType("image/png");
  axom::sina::File myOtherFile {"/path/to/other/file.txt"};
  myOtherFile.setTags({"these", "are", "tags"});

  // Create a record to store the files
  axom::sina::ID myID {"my_record", axom::sina::IDType::Local};
  std::unique_ptr<axom::sina::Record> myRecord {new axom::sina::Record {myID, "my_type"}};

  // Add the files to the record
  myRecord->add(myFile);
  myRecord->add(myOtherFile);

  // Query the record for files
  auto& files = myRecord->getFiles();
  for(const auto& file : files)
  {
    std::cout << "File with URI '" << file.getUri() << "' has mimetype '" << file.getMimeType()
              << "' and tags '";
    for(const auto& tag : file.getTags())
    {
      std::cout << tag << " ";
    }
    std::cout << "'" << std::endl;
  }
}

The above code will output:

File with URI '/path/to/file.png' has mimetype 'image/png' and tags ''
File with URI '/path/to/other/file.txt' has mimetype '' and tags 'these are tags '

Runs

Sina Run objects are a subtype of Sina Record objects corresponding to a single run of an application as specified in the Sina schema. Similar to Record objects, Run objects also require an ID; however, they do not require a type as it will automatically be set to “run”. In addition to IDs, there are a few other required fields:

  • application: the application/code used to create the Run

  • version: the version of the application used to create the Run

  • user: the username of the person who ran the application that generated this Run

The below code shows an example of how a Run can be created:

#include "axom/sina.hpp"

int main(void) {
    // Create the ID first
    axom::sina::ID run1ID{"run1", axom::sina::IDType::Local};

    // Create a run with:
    // ID: run1ID
    // application: "My Sim Code"
    // version: "1.2.3"
    // user: "jdoe"
    std::unique_ptr<sina::Record> run1{new sina::Run{run1ID, "My Sim Code", "1.2.3", "jdoe"}};
}