Skip to content

Commit 1ab6a5c

Browse files
add new graph param type and common utils to set foundation for structured parameter implementation
1 parent 62f99f1 commit 1ab6a5c

11 files changed

Lines changed: 189 additions & 56 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ set(FLUXGRAPH_SOURCES
5050
src/model/second_order_process.cpp
5151
src/model/mass_spring_damper.cpp
5252
src/model/dc_motor.cpp
53+
src/graph/param_utils.cpp
5354
src/graph/compiler.cpp
5455
src/graph/compiler/algorithms.cpp
5556
src/graph/compiler/common.cpp

include/fluxgraph/core/types.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#pragma once
22

33
#include <cstdint>
4+
#include <map>
45
#include <string>
6+
#include <type_traits>
7+
#include <vector>
58
#include <variant>
69

710
namespace fluxgraph {
@@ -69,4 +72,32 @@ struct UnitConversion {
6972
/// Variant type for command arguments and signal values
7073
using Variant = std::variant<double, int64_t, bool, std::string>;
7174

75+
class ParamValue;
76+
77+
using ParamArray = std::vector<ParamValue>;
78+
using ParamObject = std::map<std::string, ParamValue>;
79+
using ParamScalar = std::variant<double, int64_t, bool, std::string>;
80+
using ParamVariant =
81+
std::variant<double, int64_t, bool, std::string, ParamArray, ParamObject>;
82+
83+
/// Structured parameter value used for graph model/transform parameters.
84+
/// Command transport continues to use `Variant`.
85+
class ParamValue : public ParamVariant {
86+
public:
87+
using ParamVariant::ParamVariant;
88+
using ParamVariant::operator=;
89+
90+
ParamValue() : ParamVariant(double{0.0}) {}
91+
92+
ParamValue(const char *value) : ParamVariant(std::string(value)) {}
93+
94+
template <typename Integer,
95+
typename = std::enable_if_t<std::is_integral_v<Integer> &&
96+
!std::is_same_v<Integer, bool> &&
97+
!std::is_same_v<Integer, int64_t>>>
98+
ParamValue(Integer value) : ParamVariant(static_cast<int64_t>(value)) {}
99+
};
100+
101+
using ParamMap = std::map<std::string, ParamValue>;
102+
72103
} // namespace fluxgraph
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include "fluxgraph/core/types.hpp"
4+
#include <string>
5+
6+
namespace fluxgraph::param {
7+
8+
std::string type_name(const ParamValue &value);
9+
10+
double as_double(const ParamValue &value, const std::string &path);
11+
int64_t as_int64(const ParamValue &value, const std::string &path);
12+
bool as_bool(const ParamValue &value, const std::string &path);
13+
std::string as_string(const ParamValue &value, const std::string &path);
14+
const ParamArray &as_array(const ParamValue &value, const std::string &path);
15+
const ParamObject &as_object(const ParamValue &value, const std::string &path);
16+
17+
} // namespace fluxgraph::param

include/fluxgraph/graph/spec.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ struct SignalSpec {
1616
/// POD specification for a transform (protocol-agnostic)
1717
struct TransformSpec {
1818
std::string type; // "linear", "first_order_lag", "delay", etc.
19-
std::map<std::string, Variant> params;
19+
ParamMap params;
2020
};
2121

2222
/// POD specification for a signal edge in the graph
@@ -30,7 +30,7 @@ struct EdgeSpec {
3030
struct ModelSpec {
3131
std::string id; // e.g., "chamber_air"
3232
std::string type; // "thermal_mass"
33-
std::map<std::string, Variant> params;
33+
ParamMap params;
3434
};
3535

3636
/// POD specification for a rule action

src/graph/compiler/common.cpp

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,14 @@
11
#include "common.hpp"
2+
#include "fluxgraph/graph/param_utils.hpp"
23
#include <algorithm>
34
#include <cctype>
45
#include <cmath>
56
#include <stdexcept>
67

78
namespace fluxgraph::compiler_internal {
89

9-
namespace {
10-
11-
std::string variant_type_name(const Variant &value) {
12-
if (std::holds_alternative<double>(value)) {
13-
return "double";
14-
}
15-
if (std::holds_alternative<int64_t>(value)) {
16-
return "int64";
17-
}
18-
if (std::holds_alternative<bool>(value)) {
19-
return "bool";
20-
}
21-
return "string";
22-
}
23-
24-
} // namespace
25-
26-
const Variant &require_param(const std::map<std::string, Variant> &params,
27-
const std::string &name,
28-
const std::string &context) {
10+
const ParamValue &require_param(const ParamMap &params, const std::string &name,
11+
const std::string &context) {
2912
auto it = params.find(name);
3013
if (it == params.end()) {
3114
throw std::runtime_error("Missing required parameter at " + context + "/" +
@@ -34,31 +17,16 @@ const Variant &require_param(const std::map<std::string, Variant> &params,
3417
return it->second;
3518
}
3619

37-
double as_double(const Variant &value, const std::string &path) {
38-
if (std::holds_alternative<double>(value)) {
39-
return std::get<double>(value);
40-
}
41-
if (std::holds_alternative<int64_t>(value)) {
42-
return static_cast<double>(std::get<int64_t>(value));
43-
}
44-
throw std::runtime_error("Type error at " + path + ": expected number, got " +
45-
variant_type_name(value));
20+
double as_double(const ParamValue &value, const std::string &path) {
21+
return param::as_double(value, path);
4622
}
4723

48-
int64_t as_int64(const Variant &value, const std::string &path) {
49-
if (std::holds_alternative<int64_t>(value)) {
50-
return std::get<int64_t>(value);
51-
}
52-
throw std::runtime_error("Type error at " + path + ": expected int64, got " +
53-
variant_type_name(value));
24+
int64_t as_int64(const ParamValue &value, const std::string &path) {
25+
return param::as_int64(value, path);
5426
}
5527

56-
std::string as_string(const Variant &value, const std::string &path) {
57-
if (std::holds_alternative<std::string>(value)) {
58-
return std::get<std::string>(value);
59-
}
60-
throw std::runtime_error("Type error at " + path + ": expected string, got " +
61-
variant_type_name(value));
28+
std::string as_string(const ParamValue &value, const std::string &path) {
29+
return param::as_string(value, path);
6230
}
6331

6432
void require_finite(const double value, const std::string &path) {

src/graph/compiler/common.hpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@
99

1010
namespace fluxgraph::compiler_internal {
1111

12-
const Variant &require_param(const std::map<std::string, Variant> &params,
13-
const std::string &name,
14-
const std::string &context);
12+
const ParamValue &require_param(const ParamMap &params, const std::string &name,
13+
const std::string &context);
1514

16-
double as_double(const Variant &value, const std::string &path);
17-
int64_t as_int64(const Variant &value, const std::string &path);
18-
std::string as_string(const Variant &value, const std::string &path);
15+
double as_double(const ParamValue &value, const std::string &path);
16+
int64_t as_int64(const ParamValue &value, const std::string &path);
17+
std::string as_string(const ParamValue &value, const std::string &path);
1918

2019
void require_finite(double value, const std::string &path);
2120
void require_finite_positive(double value, const std::string &path);

src/graph/param_utils.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include "fluxgraph/graph/param_utils.hpp"
2+
#include <stdexcept>
3+
4+
namespace fluxgraph::param {
5+
6+
std::string type_name(const ParamValue &value) {
7+
if (std::holds_alternative<double>(value)) {
8+
return "double";
9+
}
10+
if (std::holds_alternative<int64_t>(value)) {
11+
return "int64";
12+
}
13+
if (std::holds_alternative<bool>(value)) {
14+
return "bool";
15+
}
16+
if (std::holds_alternative<std::string>(value)) {
17+
return "string";
18+
}
19+
if (std::holds_alternative<ParamArray>(value)) {
20+
return "array";
21+
}
22+
return "object";
23+
}
24+
25+
double as_double(const ParamValue &value, const std::string &path) {
26+
if (std::holds_alternative<double>(value)) {
27+
return std::get<double>(value);
28+
}
29+
if (std::holds_alternative<int64_t>(value)) {
30+
return static_cast<double>(std::get<int64_t>(value));
31+
}
32+
throw std::runtime_error("Type error at " + path + ": expected number, got " +
33+
type_name(value));
34+
}
35+
36+
int64_t as_int64(const ParamValue &value, const std::string &path) {
37+
if (std::holds_alternative<int64_t>(value)) {
38+
return std::get<int64_t>(value);
39+
}
40+
throw std::runtime_error("Type error at " + path + ": expected int64, got " +
41+
type_name(value));
42+
}
43+
44+
bool as_bool(const ParamValue &value, const std::string &path) {
45+
if (std::holds_alternative<bool>(value)) {
46+
return std::get<bool>(value);
47+
}
48+
throw std::runtime_error("Type error at " + path + ": expected bool, got " +
49+
type_name(value));
50+
}
51+
52+
std::string as_string(const ParamValue &value, const std::string &path) {
53+
if (std::holds_alternative<std::string>(value)) {
54+
return std::get<std::string>(value);
55+
}
56+
throw std::runtime_error("Type error at " + path + ": expected string, got " +
57+
type_name(value));
58+
}
59+
60+
const ParamArray &as_array(const ParamValue &value, const std::string &path) {
61+
if (std::holds_alternative<ParamArray>(value)) {
62+
return std::get<ParamArray>(value);
63+
}
64+
throw std::runtime_error("Type error at " + path + ": expected array, got " +
65+
type_name(value));
66+
}
67+
68+
const ParamObject &as_object(const ParamValue &value,
69+
const std::string &path) {
70+
if (std::holds_alternative<ParamObject>(value)) {
71+
return std::get<ParamObject>(value);
72+
}
73+
throw std::runtime_error("Type error at " + path + ": expected object, got " +
74+
type_name(value));
75+
}
76+
77+
} // namespace fluxgraph::param

src/loaders/json_loader.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ namespace fluxgraph::loaders {
1111

1212
namespace {
1313

14+
ParamValue json_to_param_value(const json &j, const std::string &path) {
15+
if (j.is_number_float()) {
16+
return j.get<double>();
17+
} else if (j.is_number_integer()) {
18+
return j.get<int64_t>();
19+
} else if (j.is_boolean()) {
20+
return j.get<bool>();
21+
} else if (j.is_string()) {
22+
return j.get<std::string>();
23+
} else {
24+
throw std::runtime_error("JSON parse error at " + path +
25+
": Unsupported type for Variant");
26+
}
27+
}
28+
1429
// Convert JSON value to Variant
1530
Variant json_to_variant(const json &j, const std::string &path) {
1631
if (j.is_number_float()) {
@@ -43,7 +58,7 @@ TransformSpec parse_transform(const json &j, const std::string &base_path) {
4358
if (j.contains("params") && j["params"].is_object()) {
4459
for (auto &[key, value] : j["params"].items()) {
4560
std::string param_path = path + "/params/" + key;
46-
spec.params[key] = json_to_variant(value, param_path);
61+
spec.params[key] = json_to_param_value(value, param_path);
4762
}
4863
}
4964

@@ -117,7 +132,7 @@ ModelSpec parse_model(const json &j, const std::string &base_path,
117132
if (j.contains("params") && j["params"].is_object()) {
118133
for (auto &[key, value] : j["params"].items()) {
119134
std::string param_path = path + "/params/" + key;
120-
spec.params[key] = json_to_variant(value, param_path);
135+
spec.params[key] = json_to_param_value(value, param_path);
121136
}
122137
}
123138

src/loaders/yaml_loader.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,31 @@ std::string format_yaml_error(const std::string &message,
1717
", column " + std::to_string(mark.column + 1) + ": " + message;
1818
}
1919

20+
ParamValue yaml_to_param_value(const YAML::Node &node, const std::string &path) {
21+
if (node.IsScalar()) {
22+
// Try to determine type from the actual content
23+
try {
24+
// Try boolean first
25+
if (node.as<std::string>() == "true" ||
26+
node.as<std::string>() == "false") {
27+
return node.as<bool>();
28+
}
29+
// Try integer
30+
if (node.as<std::string>().find('.') == std::string::npos) {
31+
return node.as<int64_t>();
32+
}
33+
// Try double
34+
return node.as<double>();
35+
} catch (...) {
36+
// Fall back to string
37+
return node.as<std::string>();
38+
}
39+
} else {
40+
throw std::runtime_error("YAML parse error at " + path +
41+
": Expected scalar value for Variant");
42+
}
43+
}
44+
2045
// Convert YAML node to Variant
2146
Variant yaml_to_variant(const YAML::Node &node, const std::string &path) {
2247
if (node.IsScalar()) {
@@ -61,7 +86,7 @@ TransformSpec parse_transform(const YAML::Node &node,
6186
for (auto it = node["params"].begin(); it != node["params"].end(); ++it) {
6287
std::string key = it->first.as<std::string>();
6388
std::string param_path = path + "/params/" + key;
64-
spec.params[key] = yaml_to_variant(it->second, param_path);
89+
spec.params[key] = yaml_to_param_value(it->second, param_path);
6590
}
6691
}
6792

@@ -131,7 +156,7 @@ ModelSpec parse_model(const YAML::Node &node, size_t index) {
131156
for (auto it = node["params"].begin(); it != node["params"].end(); ++it) {
132157
std::string key = it->first.as<std::string>();
133158
std::string param_path = path + "/params/" + key;
134-
spec.params[key] = yaml_to_variant(it->second, param_path);
159+
spec.params[key] = yaml_to_param_value(it->second, param_path);
135160
}
136161
}
137162

tests/integration/determinism_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class DeterministicCustomTransform : public ITransform {
2626
double gain_ = 1.0;
2727
};
2828

29-
double variant_to_double(const Variant &value) {
29+
double variant_to_double(const ParamValue &value) {
3030
if (std::holds_alternative<double>(value)) {
3131
return std::get<double>(value);
3232
}

0 commit comments

Comments
 (0)