SolverGraph Migration Guide¶
This guide explains how to migrate from the legacy module architecture to the modern solvergraph
architecture.
Overview¶
The solvergraph architecture provides a more structured and efficient way to manage data dependencies and memory in Shamrock. This migration represents a significant architectural improvement that:
- Provides better integration with the solver framework
- Improves memory management through implicit memory management & explicit deallocation
- Enables better visualization and debugging of data dependencies
- Supports more sophisticated data flow patterns
The complete migration from legacy modules to solvergraph involves multiple phases, including migrating data storage components (edges) and computation logic (nodes).
Edge Migration¶
Step 1: Identify the Component to Migrate¶
First, identify the shambase::StorageComponent<T>
that needs to be migrated. Look for patterns like:
// Old pattern
Component<SomeDataType> my_component;
Step 2: Determine the Appropriate SolverGraph Edge Type¶
Choose the appropriate solvergraph edge type based on your data:
Basic Data Edges¶
IDataEdgeNamed
: For simple named data with no special requirementsScalarEdge<T>
: For scalar valuesIndexes<T>
: For index arrays
Field-Based Edges¶
Field<T>
: For distributed fields with automatic memory managementFieldRefs<T>
: For references to existing fieldsFieldSpan<T>
: For field spans (performance-oriented access)
Custom Edges¶
If none of the existing edge types fit your needs, you'll need to create a custom edge.
Step 3: Create the SolverGraph Edge Implementation¶
Option A: Use Existing Edge Types¶
If your data fits an existing edge type, use it directly:
// For scalar values
std::shared_ptr<shamrock::solvergraph::ScalarEdge<T>> my_scalar;
// For index arrays
std::shared_ptr<shamrock::solvergraph::Indexes<T>> my_indexes;
// For distributed fields
std::shared_ptr<shamrock::solvergraph::Field<T>> my_field;
Option B: Create Custom Edge¶
If you need a custom edge, create a new class inheriting from IDataEdgeNamed
:
// Header file: include/yourmodel/solvergraph/YourCustomEdge.hpp
#pragma once
#include "shamrock/solvergraph/IDataEdgeNamed.hpp"
#include "your_data_type.hpp"
namespace yourmodel::solvergraph {
class YourCustomEdge : public shamrock::solvergraph::IDataEdgeNamed {
public:
using IDataEdgeNamed::IDataEdgeNamed;
// Your data members
YourDataType data;
// Implement memory management if needed
inline virtual void free_alloc() {
data = {}; // Clear your data
}
};
} // namespace yourmodel::solvergraph
Step 4: Update Storage Declaration¶
Replace the old component declaration with the new solvergraph edge:
// Old pattern
Component<YourDataType> my_component;
// New pattern
std::shared_ptr<yourmodel::solvergraph::YourCustomEdge> my_edge;
Step 5: Update Initialization¶
Update the initialization code to create the solvergraph edge:
// Old pattern
// Component was typically initialized implicitly or through storage
// New pattern
my_edge = std::make_shared<yourmodel::solvergraph::YourCustomEdge>(
"edge_name", "\\text{LaTeX Symbol}"
);
Step 6: Update Usage Patterns¶
Update how the data is accessed and used:
// Old pattern
auto& data = my_component.get();
// New pattern
// Direct access to member
auto& data = shambase::get_check_ref(my_edge).data;
// or
// If you provide accessor methods
auto& data = shambase::get_check_ref(my_edge).get_data();
Step 7: Handle Memory Management¶
Ensure proper memory management by implementing free_alloc()
if needed:
// In your custom edge implementation
inline virtual void free_alloc() {
// Clear any allocated memory
data.clear();
// or
data = {};
}
Replace instances of .reset()
on the storage component by shambase::get_check_ref(my_edge).free_alloc()
.
Common Migration Patterns¶
Pattern 1: Simple Scalar Storage¶
Before:
Component<MyDataType> my_data;
After:
std::shared_ptr<shamrock::solvergraph::ScalarEdge<MyDataType>> my_data;
Pattern 2: Distributed Data¶
Before:
Component<shambase::DistributedData<MyType>> distributed_data;
After:
std::shared_ptr<MyCustomEdge> distributed_data;
Where MyCustomEdge
inherits from IDataEdgeNamed
and contains:
shambase::DistributedData<MyType> data;
Pattern 3: Field Data¶
Before:
Component<shamrock::ComputeField<T>> field_data;
After:
std::shared_ptr<shamrock::solvergraph::Field<T>> field_data;
Edge Migration Checklist¶
- Identify the component to migrate
- Choose appropriate solvergraph edge type
- Create custom edge implementation if needed
- Update storage declaration
- Update initialization code (in init_solvergraph function)
- Update usage patterns throughout the codebase
- Replace
.reset()
by.free_alloc()
- Test the edge migration thoroughly
- Update any related documentation
Testing Your Edge Migration¶
- Compilation: Ensure the code compiles without errors
- Functionality: Verify that the migrated edge works as expected
- Memory: Check for memory leaks using appropriate tools
- Performance: Ensure performance is maintained or improved
- Integration: Test integration with other solvergraph edges
Common Pitfalls¶
- Missing Memory Management: Always implement
free_alloc()
if your edge manages memory - Incorrect Edge Type: Choose the most appropriate edge type for your data
- Incomplete Migration: Ensure all usage patterns are updated
- Missing Dependencies: Include all necessary headers for solvergraph components
Example: Complete Edge Migration¶
Here's a complete example of migrating a neighbor cache component to a solvergraph edge:
Before:
// In SolverStorage.hpp
Component<shamrock::tree::ObjectCacheHandler> neighbors_cache;
// In Solver.cpp
// Component was used implicitly
After:
// In SolverStorage.hpp
std::shared_ptr<shammodels::sph::solvergraph::NeighCache> neigh_cache;
// In Solver.cpp
storage.neigh_cache = std::make_shared<shammodels::sph::solvergraph::NeighCache>(
"neigh_cache", "neigh"
);
Custom Edge Implementation:
// In NeighCache.hpp
class NeighCache : public shamrock::solvergraph::IDataEdgeNamed {
public:
using IDataEdgeNamed::IDataEdgeNamed;
shambase::DistributedData<shamrock::tree::ObjectCache> neigh_cache;
shamrock::tree::ObjectCache &get_cache(u64 id) {
return neigh_cache.get(id);
}
inline virtual void free_alloc() {
neigh_cache = {};
}
};
Node Migration¶
TODO: This section will be documented in another PR.
Conclusion¶
This migration process provides a structured approach to moving from the legacy module system to the modern solvergraph architecture. The benefits include better memory management, improved integration, and enhanced debugging capabilities.
For additional help or questions about specific migration scenarios, consult the existing solvergraph implementations in the codebase or refer to the solvergraph API documentation.