Common Module
The common module provides the foundational abstractions that all other PhysiCore modules build upon. It defines core interfaces, data structures, and types that enable modular simulation design.
Table of Contents
- Overview
- The
timestep_executorInterface - Agent Data Structures
- Core Types
- File Organization
- Public API Stability
- Dependencies
Overview
The Common module establishes the contracts and base types that enable PhysiCore’s modular architecture. Every other module depends on Common, making it the foundation of the framework.
Namespace: physicore::common
Location: common/include/common/
Key Responsibilities:
- Define the simulation loop contract via
timestep_executor - Provide agent data structures using structure-of-arrays (SoA) pattern
- Manage agent collections through containers
- Establish core types and concepts for type safety
The timestep_executor Interface
The timestep_executor is the central abstraction that defines how simulation components participate in the main simulation loop.
Interface Definition
namespace physicore::common {
class timestep_executor {
public:
virtual ~timestep_executor() = default;
// Execute a single timestep of the simulation
virtual void run_single_timestep() = 0;
// Serialize current simulation state
virtual void serialize_state(real_t current_time) = 0;
};
} // namespace physicore::common
Design Rationale
All simulation components (diffusion solvers, mechanics engines, phenotype models) implement this interface, enabling:
- Uniform simulation loop - The main loop doesn’t need to know implementation details
- Composability - Multiple executors can be orchestrated together
- Testability - Each component can be tested in isolation
- Extensibility - New modules automatically integrate with existing infrastructure
Usage Example
#include <common/timestep_executor.h>
class DiffusionSolver : public physicore::common::timestep_executor {
public:
void run_single_timestep() override {
// Solve reaction-diffusion PDE for one timestep
compute_diffusion();
apply_reactions();
update_concentrations();
}
void serialize_state(real_t current_time) override {
// Write substrate concentrations to VTK files
vtk_writer.write(current_time, substrate_data);
}
};
Agent Data Structures
PhysiCore uses a structure-of-arrays (SoA) pattern for agent data to enable efficient vectorization and cache-friendly memory access.
Structure-of-Arrays (SoA) Pattern
Instead of storing agent data as an array of structures (AoS):
// Array-of-Structures (AoS) - NOT used in PhysiCore
struct Agent {
double x, y, z; // Position
double vx, vy, vz; // Velocity
double radius;
int type;
};
std::vector<Agent> agents; // Poor cache locality
PhysiCore uses structure-of-arrays (SoA):
// Structure-of-Arrays (SoA) - Used in PhysiCore
struct AgentData {
std::vector<double> positions; // Positions of all agents
std::vector<double> velocities; // Velocities of all agents
std::vector<double> radii;
std::vector<int> types;
};
Benefits:
- Vectorization - SIMD operations process contiguous data efficiently
- Cache efficiency - Related data is stored together
- Memory bandwidth - Fewer cache misses during computation
- Parallelization - Easy to partition across threads/GPUs
The base_agent_data class provides the foundational SoA storage for all agents in the simulation by defining an array of positions.
The base_agent Proxy
To provide object-like access to individual agents while maintaining SoA storage, PhysiCore uses a proxy pattern via the base_agent class:
namespace physicore::common {
class base_agent {
base_agent_data& data;
std::size_t index;
public:
// Constructor takes reference to data and index
base_agent(base_agent_data& data, std::size_t index);
// Access position
std::span<real_t> position() { /* accesses SoA data using its index */ }
};
} // namespace physicore::common
Key relationships:
base_agent_dataowns the SoA storage (positions array)base_agentholds a reference to the data and an indexbase_agent::position()accesses the data using its index to provide object-like interface
classDiagram
class base_agent_data {
-std::vector~real_t~ positions
+size() std::size_t
}
class base_agent {
-base_agent_data& data
-std::size_t index
+position() std::span~real_t~
}
base_agent --* base_agent_data
Core Types
The Common module defines fundamental types used throughout PhysiCore:
namespace physicore::common {
// Real number type (double precision)
using real_t = double;
// Unsigned and signed index types
using index_t = std::uint64_t;
using sindex_t = std::int64_t;
} // namespace physicore::common
These types ensure consistency and portability across modules and a single place of modification if we want to change precision later.
File Organization
The Common module follows PhysiCore’s public/private API separation:
common/
├── include/
│ └── common/ # Public API headers
│ ├── timestep_executor.h
│ ├── base_agent.h
│ ├── base_agent_data.h
│ ├── base_agent_container.h
│ ├── base_agent_interface.h
│ ├── generic_agent_container.h
│ ├── generic_agent_solver.h
│ ├── concepts.h
│ └── types.h
├── src/ # Private implementation (if needed)
└── tests/
├── test_base_agent.cpp
├── test_base_agent_data.cpp
└── test_base_agent_container.cpp
Public API Stability
Headers in common/include/common/ are exported via CMake FILE_SET HEADERS and constitute the stable public API. These interfaces are maintained across minor versions with semantic versioning guarantees.
Dependencies
The Common module has minimal external dependencies:
- C++20 standard library
- No external libraries required
This makes it the lightest-weight component and suitable as a foundation for all other modules.