Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9b5550f
Skeleton of Phlex/FORM integration module
knoepfel Dec 1, 2025
692a1ea
Merge remote-tracking branch 'origin/main' into barnali/form-integrat…
barnaliy Dec 8, 2025
71c6034
Add FORM output module for phlex product persistence
barnaliy Dec 9, 2025
cfa3d5a
Change to form::experimental::product_with_name
barnaliy Dec 9, 2025
a9c7907
1. Remove mock_phlex from test dependencies (now using form::experime…
barnaliy Dec 10, 2025
a180834
Add test_helpers.hpp with createTypeMap implementation for FORM tests
barnaliy Dec 10, 2025
8cf4609
Apply clang-format to FORM integration and test files
barnaliy Dec 10, 2025
17ba271
Remove mock_phlex directory
barnaliy Dec 10, 2025
eeffe13
Apply cmake-format fixes
github-actions[bot] Dec 10, 2025
aadbe69
Remove phlex::experimental dependency from form_module (not needed in…
barnaliy Dec 10, 2025
bf8a58c
Merge phlexbot formatting changes
barnaliy Dec 10, 2025
63e32b1
Remove phlex::experimental from form_module dependencies
barnaliy Dec 10, 2025
f68cf4d
Remove temporary comments and duplicate lines
barnaliy Dec 11, 2025
7db2419
Add integration test for form_module plugin. This test exercises Form…
barnaliy Dec 12, 2025
f3ba8dd
Trivial comment removed
barnaliy Dec 12, 2025
bd19e1f
Apply cmake-format fixes
github-actions[bot] Dec 12, 2025
2b24836
Make form_module a MODULE library
knoepfel Dec 12, 2025
08925e8
Fix form_module build: add form library dependency and enable PIC. Re…
barnaliy Dec 15, 2025
a86fa3d
Fix test failure by adding missing 'sum' product configuration. The f…
barnaliy Dec 15, 2025
5e18ba1
Add branch_name to sum product configuration. Still failing with miss…
barnaliy Dec 15, 2025
2a4f1c2
fix: Register products in form_module constructor to resolve runtime …
barnaliy Dec 16, 2025
3539c2e
fix: Add int type registration for sum product. Hardcoded product reg…
barnaliy Dec 17, 2025
59d8e91
Fix: Could not locate library with specification 'source' error. Corr…
barnaliy Dec 17, 2025
7fd6f3e
Apply cmake-format fixes
github-actions[bot] Dec 17, 2025
e62c3ab
form_test: switch from source to generate_layers
pcanal Dec 17, 2025
c6a1464
form: Properly store the TTree in its directory
pcanal Dec 17, 2025
5278497
Implementing Peter's suggestion: switch from sprintf to snprintf
barnaliy Dec 17, 2025
a1d8125
Fixing...
barnaliy Dec 17, 2025
9b49214
Form: register more supported type.
pcanal Dec 17, 2025
fdf681c
fix: Read products from config instead of hardcoding (addresses PC's …
barnaliy Dec 18, 2025
ec53af3
fix: Use simple array for products config per Kyle's suggestion
barnaliy Dec 18, 2025
d6ae99a
Commit from GitHub Actions (Clang-Format Fix)
github-actions[bot] Dec 18, 2025
56239b3
fix: Pass by const reference
barnaliy Dec 18, 2025
1e13a7e
Merge branch 'main' into barnali/form-integration-work
knoepfel Dec 18, 2025
45eae1e
Do not move products_to_save vector
knoepfel Dec 18, 2025
cd323e2
Adjust PHLEX_PLUGIN_PATH for form_module test
knoepfel Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion form/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# ##############################################################################
# Copyright (C) 2025 ...

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

include_directories(${PROJECT_SOURCE_DIR}/form)

# ROOT Storage toggle
Expand All @@ -18,11 +20,16 @@ endif()

# Add sub directories
add_subdirectory(form)
add_subdirectory(mock_phlex)
add_subdirectory(core)
add_subdirectory(util)
add_subdirectory(persistence)
add_subdirectory(storage)
if(FORM_USE_ROOT_STORAGE)
add_subdirectory(root_storage)
endif()

add_library(form_module MODULE form_module.cpp)

target_link_libraries(form_module PRIVATE phlex::module form)

target_include_directories(form_module PRIVATE ${PROJECT_SOURCE_DIR})
77 changes: 37 additions & 40 deletions form/form/form.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,88 +4,85 @@

namespace form::experimental {

// Accept and store config
form_interface::form_interface(std::shared_ptr<mock_phlex::product_type_names> tm,
mock_phlex::config::parse_config const& config) :
form_interface::form_interface(std::shared_ptr<product_type_names> tm,
config::output_item_config const& output_config,
config::tech_setting_config const& tech_config) :
m_pers(nullptr), m_type_map(tm)
{
// Convert phlex config to form config
form::experimental::config::output_item_config output_items;
for (auto const& phlex_item : config.getItems()) {
output_items.addItem(phlex_item.product_name, phlex_item.file_name, phlex_item.technology);
m_product_to_config.emplace(
phlex_item.product_name,
form::experimental::config::PersistenceItem(
phlex_item.product_name, phlex_item.file_name, phlex_item.technology));
for (auto const& item : output_config.getItems()) {
m_product_to_config.emplace(item.product_name,
form::experimental::config::PersistenceItem(
item.product_name, item.file_name, item.technology));
}

config::tech_setting_config tech_config_settings;
tech_config_settings.file_settings = config.getFileSettings();
tech_config_settings.container_settings = config.getContainerSettings();

m_pers = form::detail::experimental::createPersistence();
m_pers->configureOutputItems(output_items);
m_pers->configureTechSettings(tech_config_settings);
m_pers->configureOutputItems(output_config);
m_pers->configureTechSettings(tech_config);
}

void form_interface::write(std::string const& creator, mock_phlex::product_base const& pb)
void form_interface::write(std::string const& creator,
std::string const& segment_id,
product_with_name const& pb)
{
// Look up creator from PersistenceItem.

auto it = m_product_to_config.find(pb.label);
if (it == m_product_to_config.end()) {
throw std::runtime_error("No configuration found for product: " + pb.label);
}

std::string const type = m_type_map->names[pb.type];
// FIXME: Really only needed on first call

std::map<std::string, std::string> products = {{pb.label, type}};
m_pers->createContainers(creator, products);

m_pers->registerWrite(creator, pb.label, pb.data, type);
m_pers->commitOutput(creator, pb.id);

m_pers->commitOutput(creator, segment_id);
}

// Look up creator from config
void form_interface::write(std::string const& creator,
std::vector<mock_phlex::product_base> const& batch)
std::string const& segment_id,
std::vector<product_with_name> const& products)
{
if (batch.empty())

if (products.empty())
return;

// Look up creator from config based on product name. O(1) lookup instead of loop
auto it = m_product_to_config.find(batch[0].label);
auto it = m_product_to_config.find(products[0].label);
if (it == m_product_to_config.end()) {
throw std::runtime_error("No configuration found for product: " + batch[0].label);
throw std::runtime_error("No configuration found for product: " + products[0].label);

Check warning on line 53 in form/form/form.cpp

View check run for this annotation

Codecov / codecov/patch

form/form/form.cpp#L53

Added line #L53 was not covered by tests
}

// FIXME: Really only needed on first call
std::map<std::string, std::string> products;
for (auto const& pb : batch) {
std::map<std::string, std::string> product_types;
for (auto const& pb : products) {
std::string const& type = m_type_map->names[pb.type];
products.insert(std::make_pair(pb.label, type));
product_types.insert(std::make_pair(pb.label, type));
}
m_pers->createContainers(creator, products);
for (auto const& pb : batch) {

m_pers->createContainers(creator, product_types);

for (auto const& pb : products) {
std::string const& type = m_type_map->names[pb.type];
// FIXME: We could consider checking id to be identical for all product bases here
m_pers->registerWrite(creator, pb.label, pb.data, type);
}
// Single commit per segment (product ID shared among products in the same segment)
std::string const& id = batch[0].id;
m_pers->commitOutput(creator, id);

m_pers->commitOutput(creator, segment_id);
}

void form_interface::read(std::string const& creator, mock_phlex::product_base& pb)
void form_interface::read(std::string const& creator,
std::string const& segment_id,
product_with_name& pb)
{
// Look up creator from config based on product name. O(1) lookup instead of loop

auto it = m_product_to_config.find(pb.label);
if (it == m_product_to_config.end()) {
throw std::runtime_error("No configuration found for product: " + pb.label);
}

// Original type lookup
std::string type = m_type_map->names[pb.type];

// Use full_label instead of pb.label
m_pers->read(creator, pb.label, pb.id, &pb.data, type);
m_pers->read(creator, pb.label, segment_id, &pb.data, type);
}
}
38 changes: 29 additions & 9 deletions form/form/form.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,49 @@
#define __FORM_HPP__

#include "form/config.hpp"
#include "mock_phlex/phlex_toy_config.hpp"
#include "mock_phlex/phlex_toy_core.hpp" // FORM Interface may include core phlex modules
#include "persistence/ipersistence.hpp"

#include <map>
#include <memory>
#include <string>
#include <typeindex>
#include <unordered_map>
#include <vector>

namespace form::experimental {

struct product_type_names {
std::unordered_map<std::type_index, std::string> names;
};

struct product_with_name {
std::string label;
void const* data;
std::type_index type;
};

class form_interface {
public:
form_interface(std::shared_ptr<mock_phlex::product_type_names> tm,
mock_phlex::config::parse_config const& config);
form_interface(std::shared_ptr<product_type_names> tm,
config::output_item_config const& output_config,
config::tech_setting_config const& tech_config);
~form_interface() = default;

void write(std::string const& creator, mock_phlex::product_base const& pb);
void write(std::string const& creator,
std::vector<mock_phlex::product_base> const& batch); // batch version
void read(std::string const& creator, mock_phlex::product_base& pb);
std::string const& segment_id,
product_with_name const& product);

void write(std::string const& creator,
std::string const& segment_id,
std::vector<product_with_name> const& products);

void read(std::string const& creator,
std::string const& segment_id,
product_with_name& product);

private:
std::unique_ptr<form::detail::experimental::IPersistence> m_pers;
std::shared_ptr<mock_phlex::product_type_names> m_type_map;
// Fast lookup maps built once in constructor
std::shared_ptr<product_type_names> m_type_map;
std::map<std::string, form::experimental::config::PersistenceItem> m_product_to_config;
};
}
Expand Down
153 changes: 153 additions & 0 deletions form/form_module.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#include "phlex/model/product_store.hpp"
#include "phlex/model/products.hpp"
#include "phlex/module.hpp"

// FORM headers - these need to be available via CMake configuration
// need to set up the build system to find these headers
#include "form/config.hpp"
#include "form/form.hpp"
#include "form/technology.hpp"

#include <iostream>

namespace {

class FormOutputModule {
public:
FormOutputModule(std::shared_ptr<form::experimental::product_type_names> type_map,
std::string output_file,
int technology) :
m_type_map(type_map), m_output_file(std::move(output_file)), m_technology(technology)
{
std::cout << "FormOutputModule initialized\n";
std::cout << " Output file: " << m_output_file << "\n";
std::cout << " Technology: " << m_technology << "\n";

// Build FORM configuration
form::experimental::config::output_item_config output_cfg;
form::experimental::config::tech_setting_config tech_cfg;

// FIXME: Temporary solution to accommodate Phlex limitation.
// Eventually, Phlex will communicate to FORM which products will be written
// before executing any algorithms

// Temp. Sol for Phlex Prototype 0.1
// Register products that will be written
// Register "sum" product with output file and technology
output_cfg.addItem("sum", m_output_file, m_technology);

std::cout << " Registered product 'sum' with FORM\n";

// Initialize FORM interface
m_form_interface =
std::make_unique<form::experimental::form_interface>(type_map, output_cfg, tech_cfg);
}

// This method is called by Phlex - signature must be: void(product_store const&)
void save_data_products(phlex::experimental::product_store const& store)
{
// Check if store is empty - smart way, check store not products vector
if (store.empty()) {
return;
}

// STEP 1: Extract metadata from Phlex's product_store

// Extract creator (algorithm name)
std::string creator = store.source();

// Extract segment ID (partition) - extract once for entire store
std::string segment_id = store.id()->to_string();

std::cout << "\n=== FormOutputModule::save_data_products ===\n";
std::cout << "Creator: " << creator << "\n";
std::cout << "Segment ID: " << segment_id << "\n";
std::cout << "Number of products: " << store.size() << "\n";

// STEP 2: Convert each Phlex product to FORM format

// Collect all products for writing
std::vector<form::experimental::product_with_name> products;

// Reserve space for efficiency - avoid reallocations
products.reserve(store.size());

// Iterate through all products in the store
for (auto const& [product_name, product_ptr] : store) {
// product_name: "tracks" (from the map key)
// product_ptr: pointer to the actual product data

std::cout << " Product: " << product_name << "\n";

// Create FORM product with metadata
products.emplace_back(product_name, // label, from map key
product_ptr->address(), // data, from phlex product_base
product_ptr->type() // type, from phlex product_base
);
}

// STEP 3: Send everything to FORM for persistence

// Write all products to FORM
// Pass segment_id once for entire collection (not duplicated in each product)
// No need to check if products is empty - already checked store.empty() above
m_form_interface->write(creator, segment_id, products);
std::cout << "Wrote " << products.size() << " products to FORM\n";
}

private:
std::shared_ptr<form::experimental::product_type_names> m_type_map;
std::string m_output_file;
int m_technology;
std::unique_ptr<form::experimental::form_interface> m_form_interface;
};

}

PHLEX_EXPERIMENTAL_REGISTER_ALGORITHMS(m, config)
{
std::cout << "Registering FORM output module...\n";

// Create type map
auto type_map = std::make_shared<form::experimental::product_type_names>();

// Register the int type for sum product
type_map->names[std::type_index(typeid(int))] = "int";

// TODO: Register product types
// This needs to be populated with actual types your application uses
// Example:
// type_map->names[std::type_index(typeid(TrackData))] = "TrackData";
// type_map->names[std::type_index(typeid(HitCollection))] = "HitCollection";

// Extract configuration from Phlex config
std::string output_file = config.get<std::string>("output_file", "output.root");
std::string tech_string = config.get<std::string>("technology", "ROOT_TTREE");

std::cout << "Configuration:\n";
std::cout << " output_file: " << output_file << "\n";
std::cout << " technology: " << tech_string << "\n";

// Map Phlex config string to FORM technology constant
int technology = form::technology::ROOT_TTREE; // default

if (tech_string == "ROOT_TTREE") {
technology = form::technology::ROOT_TTREE;
} else if (tech_string == "ROOT_RNTUPLE") {
technology = form::technology::ROOT_RNTUPLE;

Check warning on line 137 in form/form_module.cpp

View check run for this annotation

Codecov / codecov/patch

form/form_module.cpp#L137

Added line #L137 was not covered by tests
} else if (tech_string == "HDF5") {
technology = form::technology::HDF5;
} else {
throw std::runtime_error("Unknown technology: " + tech_string);

Check warning on line 141 in form/form_module.cpp

View check run for this annotation

Codecov / codecov/patch

form/form_module.cpp#L139-L141

Added lines #L139 - L141 were not covered by tests
}

// Phlex needs an OBJECT
// Create the FORM output module
auto form_output = m.make<FormOutputModule>(type_map, output_file, technology);

// Phlex needs a MEMBER FUNCTION to call
// Register the callback that Phlex will invoke
form_output.output("save_data_products", &FormOutputModule::save_data_products);

std::cout << "FORM output module registered successfully\n";
}
1 change: 0 additions & 1 deletion form/mock_phlex/CMakeLists.txt

This file was deleted.

Loading
Loading