Skip to content

Commit b18001a

Browse files
committed
included pyCGNS list aliasing for enhanced interoperability
1 parent 24e92f8 commit b18001a

File tree

5 files changed

+585
-142
lines changed

5 files changed

+585
-142
lines changed

include/io/cgns/node_pycgns_converter.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010

1111
namespace py = pybind11;
1212

13+
namespace pycgnsinterop {
14+
15+
/**
16+
* @brief Synchronize the aliased Python CGNS list attached to a Node, when one exists.
17+
* @param node Node whose aliased pyCGNS view should be refreshed.
18+
*/
19+
void synchronizeAliasedPyCGNSIfPresent(const std::shared_ptr<Node>& node);
20+
21+
} // namespace pycgnsinterop
22+
1323
/**
1424
* @brief Convert a Node hierarchy to a Python CGNS-like nested list.
1525
* @param node Root node to convert.
Lines changed: 210 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,265 @@
11
#include "io/cgns/node_pycgns_converter.hpp"
22

3+
#include <unordered_map>
4+
35
#include "array/array_numpy_bridge.hpp"
46

5-
/**
6-
* @brief Convert a C++ Node object into a Python CGNS-like structure (list).
7-
* @param node The Node to convert.
8-
* @return A pybind11 list representing the Node in CGNS format.
9-
*/
10-
py::list nodeToPyCGNS(const std::shared_ptr<Node>& node) {
11-
py::list nodePyList;
7+
namespace {
128

13-
// Append node name
14-
nodePyList.append(node->name());
9+
struct AliasedPyCGNSNode {
10+
std::weak_ptr<Node> node;
11+
py::list pyList;
12+
};
13+
14+
using AliasedPyCGNSRegistry = std::unordered_map<const Node*, AliasedPyCGNSNode>;
15+
16+
AliasedPyCGNSRegistry& aliasedPyCGNSRegistry() {
17+
static auto* registry = new AliasedPyCGNSRegistry();
18+
return *registry;
19+
}
20+
21+
void removeExpiredAliasedNodes() {
22+
auto& registry = aliasedPyCGNSRegistry();
23+
for (auto it = registry.begin(); it != registry.end(); ) {
24+
if (it->second.node.expired()) {
25+
it = registry.erase(it);
26+
} else {
27+
++it;
28+
}
29+
}
30+
}
31+
32+
void rememberAliasedPyCGNS(const std::shared_ptr<Node>& node, const py::list& pyList) {
33+
if (!node) {
34+
return;
35+
}
36+
37+
removeExpiredAliasedNodes();
38+
aliasedPyCGNSRegistry()[node.get()] = AliasedPyCGNSNode{node, pyList};
39+
}
40+
41+
py::object aliasedPyCGNSOrNone(const std::shared_ptr<Node>& node) {
42+
if (!node) {
43+
return py::none();
44+
}
45+
46+
removeExpiredAliasedNodes();
47+
48+
const auto it = aliasedPyCGNSRegistry().find(node.get());
49+
if (it == aliasedPyCGNSRegistry().end()) {
50+
return py::none();
51+
}
52+
53+
if (it->second.node.expired()) {
54+
aliasedPyCGNSRegistry().erase(it);
55+
return py::none();
56+
}
57+
58+
return py::reinterpret_borrow<py::object>(it->second.pyList);
59+
}
1560

16-
// Convert node data (value)
61+
void validatePyCGNSNodeList(const py::list& pyList) {
62+
if (!py::isinstance<py::list>(pyList) || py::len(pyList) != 4) {
63+
throw std::invalid_argument("pyCGNSToNode: Input must be a 4-element Python list.");
64+
}
65+
}
66+
67+
py::object nodeValueToPyCGNS(const std::shared_ptr<Node>& node) {
1768
if (node->hasLinkTarget()) {
1869
py::list linkValue;
1970
linkValue.append("target_file:" + node->linkTargetFile());
2071
linkValue.append("target_path:" + node->linkTargetPath());
21-
nodePyList.append(linkValue);
22-
} else {
23-
std::shared_ptr<Data> dataPtr = node->dataPtr();
24-
if (!dataPtr || dataPtr->isNone()) {
25-
nodePyList.append(py::none());
26-
} else if (auto arrayPtr = std::dynamic_pointer_cast<Array>(dataPtr)) {
27-
nodePyList.append(arraybridge::toPyObject(*arrayPtr));
28-
} else {
29-
throw std::runtime_error("nodeToPyCGNS: Unsupported Data subclass.");
30-
}
72+
return std::move(linkValue);
3173
}
3274

33-
// Convert children to Python list
34-
py::list children_pylist;
35-
for (const auto& child : node->children()) {
36-
children_pylist.append(nodeToPyCGNS(child));
75+
std::shared_ptr<Data> dataPtr = node->dataPtr();
76+
if (!dataPtr || dataPtr->isNone()) {
77+
return py::none();
78+
}
79+
80+
if (auto arrayPtr = std::dynamic_pointer_cast<Array>(dataPtr)) {
81+
return arraybridge::toPyObject(*arrayPtr);
82+
}
83+
84+
throw std::runtime_error("nodeToPyCGNS: Unsupported Data subclass.");
85+
}
86+
87+
py::list buildPyCGNSSnapshot(const std::shared_ptr<Node>& node);
88+
py::list buildRegisteredPyCGNS(const std::shared_ptr<Node>& node);
89+
void synchronizeAliasedPyCGNSRecursive(const std::shared_ptr<Node>& node);
90+
91+
py::list getOrCreateAliasedPyCGNS(const std::shared_ptr<Node>& node) {
92+
py::object aliasedPyObject = aliasedPyCGNSOrNone(node);
93+
if (aliasedPyObject.is_none()) {
94+
return buildRegisteredPyCGNS(node);
3795
}
38-
nodePyList.append(children_pylist);
3996

40-
// Append node type
97+
synchronizeAliasedPyCGNSRecursive(node);
98+
return aliasedPyObject.cast<py::list>();
99+
}
100+
101+
py::list buildPyCGNSNodeList(const std::shared_ptr<Node>& node, bool registerAlias) {
102+
py::list nodePyList;
103+
py::list childrenPyList;
104+
105+
nodePyList.append(node->name());
106+
nodePyList.append(nodeValueToPyCGNS(node));
107+
nodePyList.append(childrenPyList);
41108
nodePyList.append(node->type());
42109

110+
if (registerAlias) {
111+
rememberAliasedPyCGNS(node, nodePyList);
112+
}
113+
114+
for (const auto& child : node->children()) {
115+
if (registerAlias) {
116+
childrenPyList.append(buildRegisteredPyCGNS(child));
117+
} else {
118+
childrenPyList.append(buildPyCGNSSnapshot(child));
119+
}
120+
}
121+
43122
return nodePyList;
44123
}
45124

46-
/**
47-
* @brief Convert a Python CGNS-like list into a C++ Node object.
48-
* @param pyList A 4-element list in CGNS format.
49-
* @return A shared_ptr<Node> representing the converted Node.
50-
*/
51-
std::shared_ptr<Node> pyCGNSToNode(const py::list& pyList) {
52-
if (!py::isinstance<py::list>(pyList) || py::len(pyList) != 4) {
53-
throw std::invalid_argument("pyCGNSToNode: Input must be a 4-element Python list.");
125+
py::list buildPyCGNSSnapshot(const std::shared_ptr<Node>& node) {
126+
return buildPyCGNSNodeList(node, false);
127+
}
128+
129+
py::list buildRegisteredPyCGNS(const std::shared_ptr<Node>& node) {
130+
return buildPyCGNSNodeList(node, true);
131+
}
132+
133+
py::list mutableChildrenList(const py::list& nodePyList) {
134+
if (py::isinstance<py::list>(nodePyList[2])) {
135+
return py::cast<py::list>(nodePyList[2]);
54136
}
55137

56-
// Extract name
57-
std::string name = py::cast<std::string>(pyList[0]);
138+
py::list childrenPyList;
139+
nodePyList[2] = childrenPyList;
140+
return childrenPyList;
141+
}
58142

59-
// Extract type
60-
std::string type = py::cast<std::string>(pyList[3]);
143+
void synchronizeAliasedPyCGNSRecursive(const std::shared_ptr<Node>& node) {
144+
py::object aliasedPyObject = aliasedPyCGNSOrNone(node);
145+
if (aliasedPyObject.is_none()) {
146+
return;
147+
}
61148

62-
// Extract children
63-
std::shared_ptr<Node> node = std::make_shared<Node>(name, type);
149+
py::list nodePyList = aliasedPyObject.cast<py::list>();
150+
nodePyList[0] = py::cast(node->name());
151+
nodePyList[1] = nodeValueToPyCGNS(node);
152+
153+
py::list childrenPyList = mutableChildrenList(nodePyList);
154+
childrenPyList.attr("clear")();
155+
for (const auto& child : node->children()) {
156+
childrenPyList.append(getOrCreateAliasedPyCGNS(child));
157+
}
64158

65-
py::object valueObj = pyList[1].cast<py::object>();
159+
nodePyList[3] = py::cast(node->type());
160+
}
161+
162+
void populateNodeValueFromPyCGNS(const std::shared_ptr<Node>& node, const py::object& valueObj) {
66163
if (py::isinstance<py::array>(valueObj)) {
67164
node->setData(arraybridge::arrayFromPyArray(py::cast<py::array>(valueObj)));
68-
} else if (py::isinstance<py::none>(valueObj)) {
69-
// Keep default none-data.
70-
} else if (py::isinstance<py::list>(valueObj)) {
165+
return;
166+
}
167+
168+
if (valueObj.is_none()) {
169+
return;
170+
}
171+
172+
if (py::isinstance<py::list>(valueObj)) {
71173
py::list linkValue = py::cast<py::list>(valueObj);
72174
std::string targetFile;
73175
std::string targetPath;
176+
74177
for (const auto& item : linkValue) {
75-
if (!py::isinstance<py::str>(item)) continue;
178+
if (!py::isinstance<py::str>(item)) {
179+
continue;
180+
}
181+
76182
const std::string token = py::cast<std::string>(item);
77183
if (token.rfind("target_file:", 0) == 0) {
78184
targetFile = token.substr(std::string("target_file:").size());
79185
} else if (token.rfind("target_path:", 0) == 0) {
80186
targetPath = token.substr(std::string("target_path:").size());
81187
}
82188
}
189+
83190
if (targetPath.empty()) {
84191
throw std::invalid_argument("pyCGNSToNode: Link value list must define target_path.");
85192
}
193+
86194
node->setLinkTarget(targetFile, targetPath);
87-
} else {
88-
throw std::invalid_argument("pyCGNSToNode: Second element must be NumPy array, None, or Link value list.");
195+
return;
89196
}
90197

198+
throw std::invalid_argument(
199+
"pyCGNSToNode: Second element must be NumPy array, None, or Link value list.");
200+
}
201+
202+
std::shared_ptr<Node> pyCGNSToNodeRecursive(const py::list& pyList) {
203+
validatePyCGNSNodeList(pyList);
204+
205+
const std::string name = py::cast<std::string>(pyList[0]);
206+
const std::string type = py::cast<std::string>(pyList[3]);
207+
208+
std::shared_ptr<Node> node = std::make_shared<Node>(name, type);
209+
rememberAliasedPyCGNS(node, pyList);
210+
211+
populateNodeValueFromPyCGNS(node, pyList[1].cast<py::object>());
212+
91213
if (!py::isinstance<py::list>(pyList[2])) {
92214
throw std::invalid_argument("pyCGNSToNode: Third element must be a list.");
93215
}
94216

95217
py::list children = py::cast<py::list>(pyList[2]);
96218
for (const auto& child : children) {
97-
if (py::isinstance<py::list>(child)) {
98-
node->addChild(pyCGNSToNode(py::cast<py::list>(child))); // Recursively create child nodes
99-
} else {
219+
if (!py::isinstance<py::list>(child)) {
100220
throw std::invalid_argument("pyCGNSToNode: Child must be a list.");
101221
}
222+
223+
node->addChild(pyCGNSToNodeRecursive(py::cast<py::list>(child)));
102224
}
103225

104226
return node;
105227
}
228+
229+
} // namespace
230+
231+
namespace pycgnsinterop {
232+
233+
void synchronizeAliasedPyCGNSIfPresent(const std::shared_ptr<Node>& node) {
234+
if (!node) {
235+
return;
236+
}
237+
238+
synchronizeAliasedPyCGNSRecursive(node);
239+
}
240+
241+
} // namespace pycgnsinterop
242+
243+
/**
244+
* @brief Convert a C++ Node object into a Python CGNS-like structure (list).
245+
* @param node The Node to convert.
246+
* @return A pybind11 list representing the Node in CGNS format.
247+
*/
248+
py::list nodeToPyCGNS(const std::shared_ptr<Node>& node) {
249+
py::object aliasedPyObject = aliasedPyCGNSOrNone(node);
250+
if (!aliasedPyObject.is_none()) {
251+
synchronizeAliasedPyCGNSRecursive(node);
252+
return aliasedPyObject.cast<py::list>();
253+
}
254+
255+
return buildPyCGNSSnapshot(node);
256+
}
257+
258+
/**
259+
* @brief Convert a Python CGNS-like list into a C++ Node object.
260+
* @param pyList A 4-element list in CGNS format.
261+
* @return A shared_ptr<Node> representing the converted Node.
262+
*/
263+
std::shared_ptr<Node> pyCGNSToNode(const py::list& pyList) {
264+
return pyCGNSToNodeRecursive(pyList);
265+
}

0 commit comments

Comments
 (0)