Program Listing for File parameter_block.h

Return to documentation for file (framework/parameters/parameter_block.h)

// SPDX-FileCopyrightText: 2024 The OpenSn Authors <https://open-sn.github.io/opensn/>
// SPDX-License-Identifier: MIT

#pragma once

#include "framework/data_types/varying.h"
#include <memory>
#include <stdexcept>
#include <vector>
#include <string>
#include <map>

namespace opensn
{

enum class ParameterBlockType
{
  INVALID_VALUE = 0,
  BOOLEAN = 1,
  FLOAT = 3,
  STRING = 4,
  INTEGER = 5,
  USER_DATA = 6,
  ARRAY = 98,
  BLOCK = 99
};

std::string ParameterBlockTypeName(ParameterBlockType type);

class ParameterBlock;

/**
 * A ParameterBlock is a conceptually simple data structure that supports a hierarchy of primitive
 * parameters. There really are just 4 member variables on a ParameterBlock object, they are 1) the
 * type (as an enum), 2) the name of the block, 3) a pointer to a value (which can only be a
 * primitive type), and 4) a vector of child parameters.
 *
 * If a ParameterBlock has a primitive type, i.e., BOOLEAN, FLOAT, STRING, or INTEGER, then the
 * value_ptr will contain a pointer to the value of a primitive type. Otherwise, for types ARRAY and
 * BLOCK, the ParameterBlock will not have a value_ptr and instead the vector member will contain
 * sub-parameters.
 */
class ParameterBlock
{
private:
  ParameterBlockType type_ = ParameterBlockType::BLOCK;
  std::string name_;
  std::shared_ptr<Varying> value_ptr_ = nullptr;
  std::vector<ParameterBlock> parameters_;
  std::string error_origin_scope_ = "Unknown Scope";

public:
  /// Sets the name of the block.
  void SetBlockName(const std::string& name);

  // Helpers
  template <typename T>
  struct IsBool
  {
    static constexpr bool value = std::is_same_v<T, bool>;
  };
  template <typename T>
  struct IsFloat
  {
    static constexpr bool value = std::is_floating_point_v<T>;
  };
  template <typename T>
  struct IsString
  {
    static constexpr bool value = std::is_same_v<T, std::string> or std::is_same_v<T, const char*>;
  };
  template <typename T>
  struct IsInteger
  {
    static constexpr bool value = std::is_integral_v<T> and not std::is_same_v<T, bool>;
  };
  template <typename T>
  struct IsUserData
  {
    static constexpr bool value = (std::is_pointer_v<T> or is_shared_ptr_v<T> or
                                   (std::is_class_v<T> and not std::is_same_v<T, std::string>)) and
                                  (not std::is_same_v<T, const char*>);
  };

  // Constructors
  /// Constructs an empty parameter block with the given name and type BLOCK.
  explicit ParameterBlock(const std::string& name = "");

  /// Derived type constructor
  template <typename T>
  ParameterBlock(const std::string& name, const std::vector<T>& array)
    : type_(ParameterBlockType::ARRAY), name_(name)
  {
    size_t k = 0;
    for (const T& value : array)
      AddParameter(std::to_string(k++), value);
  }

  /// Constructs one of the fundamental types.
  template <typename T>
  explicit ParameterBlock(const std::string& name, T value) : name_(name)
  {
    constexpr bool is_supported = IsBool<T>::value or IsFloat<T>::value or IsString<T>::value or
                                  IsInteger<T>::value or IsUserData<T>::value;

    static_assert(is_supported, "Value type not supported for parameter block");

    if (IsBool<T>::value)
      type_ = ParameterBlockType::BOOLEAN;
    if (IsFloat<T>::value)
      type_ = ParameterBlockType::FLOAT;
    if (IsString<T>::value)
      type_ = ParameterBlockType::STRING;
    if (IsInteger<T>::value)
      type_ = ParameterBlockType::INTEGER;
    if (IsUserData<T>::value)
      type_ = ParameterBlockType::USER_DATA;

    value_ptr_ = std::make_shared<Varying>(value);
  }

  /// Copy constructor
  ParameterBlock(const ParameterBlock& other);

  /// Copy assignment operator
  ParameterBlock& operator=(const ParameterBlock& other);

  /// Move constructor
  ParameterBlock(ParameterBlock&& other) noexcept;

  /// Move assignment operator
  ParameterBlock& operator=(ParameterBlock&& other) noexcept;

  // Accessors
  ParameterBlockType GetType() const;

  /**
   * Returns true if the parameter block comprises a single value of any of the types BOOLEAN,
   * FLOAT, STRING, INTEGER.
   */
  bool IsScalar() const;

  /// Returns a string version of the type.
  std::string GetTypeName() const;
  std::string GetName() const;
  const Varying& GetValue() const;

  /// Returns the number of parameters in a block. This is normally only useful for the ARRAY type.
  size_t GetNumParameters() const;

  /// Returns the sub-parameters of this block.
  const std::vector<ParameterBlock>& GetParameters() const;

  /**
   * Returns whether or not the block has a value. If this block has sub-parameters it should not
   * have a value. This is a good way to check if the block is actually a single value because some
   * Parameter blocks can be passed as empty.
   */
  bool HasValue() const;

  // Mutators

  /// Changes the block type to array, making it accessible via integer keys.
  void ChangeToArray();

  /// Sets a string to be displayed alongside exceptions that give some notion of the origin of the
  /// error.
  void SetErrorOriginScope(const std::string& scope);

  /// Gets a string that allows error messages to print the scope of an error.
  std::string GetErrorOriginScope() const { return error_origin_scope_; }

  // Requirements

  /**
   * Checks that the block is of the given type. If it is not it will throw an exception
   * `std::logic_error`.
   */
  void RequireBlockTypeIs(ParameterBlockType type) const;
  void RequireParameterBlockTypeIs(const std::string& param_name, ParameterBlockType type) const
  {
    GetParam(param_name).RequireBlockTypeIs(type);
  }

  /// Check that the parameter with the given name exists otherwise throws a `std::logic_error`.
  void RequireParameter(const std::string& param_name) const;

  // utilities

  /// Adds a parameter to the sub-parameter list.
  void AddParameter(ParameterBlock block);

  /// Makes a ParameterBlock and adds it to the sub-parameters list.
  template <typename T>
  void AddParameter(const std::string& name, const T& value)
  {
    AddParameter(ParameterBlock(name, value));
  }

  /// Sorts the sub-parameter list according to name. This is useful for regression testing.
  void SortParameters();

  /// Returns true if a parameter with the specified name is in the list of sub-parameters.
  /// Otherwise, false.
  bool Has(const std::string& param_name) const;

  /// Gets a parameter by name.
  ParameterBlock& GetParam(const std::string& param_name);

  /// Gets a parameter by index.
  ParameterBlock& GetParam(size_t index);

  /// Gets a parameter by name.
  const ParameterBlock& GetParam(const std::string& param_name) const;

  /// Gets a parameter by index.
  const ParameterBlock& GetParam(size_t index) const;

  /// Returns the value of the parameter.
  template <typename T>
  T GetValue() const
  {
    if (value_ptr_ == nullptr)
      throw std::logic_error(error_origin_scope_ + std::string(__PRETTY_FUNCTION__) +
                             ": Value not available for block type " +
                             ParameterBlockTypeName(GetType()));
    try
    {
      return GetValue().GetValue<T>();
    }
    catch (const std::exception& exc)
    {
      throw std::logic_error(error_origin_scope_ + ":" + GetName() + " " + exc.what());
    }
  }

  /// Fetches the parameter with the given name and returns it value.
  template <typename T>
  T GetParamValue(const std::string& param_name) const
  {
    try
    {
      const auto& param = GetParam(param_name);
      return param.GetValue<T>();
    }
    catch (const std::out_of_range& oor)
    {
      throw std::out_of_range(error_origin_scope_ + std::string(__PRETTY_FUNCTION__) +
                              ": Parameter \"" + param_name + "\" not present in block");
    }
  }

  /**
   * Fetches the parameter of type std::shared_ptr<T> with the given name and returns its value.
   *
   * Will perform checking on whether or not the pointed-to-object is null (if \p check = true)
   *
   * The optional second template argument can be used to attempt to cast the object
   * to the derived type and will throw an exception if the cast fails.
   */
  template <typename T, typename Derived = T>
  std::shared_ptr<Derived> GetSharedPtrParam(const std::string& param_name,
                                             const bool check = true) const
  {
    static_assert(std::is_base_of_v<T, Derived>, "T is not a base of derived");

    auto value = this->GetParamValue<std::shared_ptr<T>>(param_name);
    if (!value)
    {
      if (check)
        throw std::logic_error(error_origin_scope_ + std::string(__PRETTY_FUNCTION__) +
                               ": shared_ptr param is null");
      return nullptr;
    }
    if constexpr (!std::is_same_v<T, Derived>)
    {
      if (auto derived_value = std::dynamic_pointer_cast<Derived>(value))
        return derived_value;

      throw std::logic_error(error_origin_scope_ + std::string(__PRETTY_FUNCTION__) +
                             ": Supplied object is not derived from " + typeid(T).name());
    }
    else
    {
      return value;
    }
  }

  /**
   * Converts the parameters of an array-type parameter block to a vector of primitive types and
   * returns it.
   */
  template <typename T>
  std::vector<T> GetVectorValue() const
  {
    if (GetType() != ParameterBlockType::ARRAY)
      throw std::logic_error(error_origin_scope_ + std::string(__PRETTY_FUNCTION__) +
                             ": Invalid type requested for parameter of type " +
                             ParameterBlockTypeName(GetType()));

    std::vector<T> vec;
    if (parameters_.empty())
      return vec;

    // Check the first sub-param is of the right type
    const auto& front_param = parameters_.front();

    // Check that all other parameters are of the required type
    for (const auto& param : parameters_)
      if (param.GetType() != front_param.GetType())
        throw std::logic_error(error_origin_scope_ + " " + std::string(__PRETTY_FUNCTION__) +
                               ": Parameter \"" + name_ +
                               "\", cannot construct vector from block because "
                               "the sub_parameters do not all have the correct type. param->" +
                               ParameterBlockTypeName(param.GetType()) + " vs param0->" +
                               ParameterBlockTypeName(front_param.GetType()));

    const size_t num_params = parameters_.size();
    for (size_t k = 0; k < num_params; ++k)
    {
      const auto& param = GetParam(k);
      vec.push_back(param.GetValue<T>());
    }

    return vec;
  }

  /// Gets a vector of primitive types from an array-type parameter block specified as a parameter
  /// of the current block.
  template <typename T>
  std::vector<T> GetParamVectorValue(const std::string& param_name) const
  {
    const auto& param = GetParam(param_name);
    return param.GetVectorValue<T>();
  }

  // Iterator
  class Iterator
  {
  public:
    ParameterBlock& ref_block;
    size_t ref_id;

    Iterator(ParameterBlock& block, size_t i) : ref_block(block), ref_id(i) {}

    Iterator operator++()
    {
      Iterator i = *this;
      ref_id++;
      return i;
    }
    Iterator operator++(int)
    {
      ref_id++;
      return *this;
    }

    ParameterBlock& operator*() { return ref_block.parameters_[ref_id]; }
    bool operator==(const Iterator& rhs) const { return ref_id == rhs.ref_id; }
    bool operator!=(const Iterator& rhs) const { return ref_id != rhs.ref_id; }
  };

  class ConstIterator
  {
  public:
    const ParameterBlock& ref_block;
    size_t ref_id;

    ConstIterator(const ParameterBlock& block, size_t i) : ref_block(block), ref_id(i) {}

    ConstIterator operator++()
    {
      ConstIterator i = *this;
      ref_id++;
      return i;
    }
    ConstIterator operator++(int)
    {
      ref_id++;
      return *this;
    }

    const ParameterBlock& operator*() { return ref_block.parameters_[ref_id]; }
    bool operator==(const ConstIterator& rhs) const { return ref_id == rhs.ref_id; }
    bool operator!=(const ConstIterator& rhs) const { return ref_id != rhs.ref_id; }
  };

  Iterator begin() { return {*this, 0}; }
  Iterator end() { return {*this, parameters_.size()}; }

  ConstIterator begin() const { return {*this, 0}; }
  ConstIterator end() const { return {*this, parameters_.size()}; }

  /**
   * Given a reference to a string, recursively travels the parameter tree and print values into
   * the reference string.
   */
  void RecursiveDumpToString(std::string& outstr, const std::string& offset = "") const;

  /// Print the block tree structure into a designated string.
  void RecursiveDumpToJSON(std::string& outstr) const;
};

} // namespace opensn