Skip to content

restructuring of handler for variables of interest#392

Draft
allaffa wants to merge 35 commits into
ORNL:mainfrom
allaffa:variables_of_interest_restructured
Draft

restructuring of handler for variables of interest#392
allaffa wants to merge 35 commits into
ORNL:mainfrom
allaffa:variables_of_interest_restructured

Conversation

@allaffa

@allaffa allaffa commented Nov 4, 2025

Copy link
Copy Markdown
Collaborator

Restructure Variables of Interest Configuration System

This commit introduces a new self-documenting feature configuration format
for Variables_of_interest while maintaining full backward compatibility.

Key Changes:

  • New self-documenting JSON format with explicit roles
  • Automatic feature parsing and validation
  • Removed ~200+ lines of redundant code
  • Added comprehensive validation utilities

Core Components:

  • feature_config.py (NEW)
  • config_utils.py (MODIFIED)

Examples Migrated: 16
Test Configs Migrated: 5

Testing:
✅ All validation tests passed
✅ Legacy format backward compatibility verified
✅ Code formatted with black
✅ No breaking changes

Related: #392

This commit introduces a new self-documenting feature configuration format
for Variables_of_interest while maintaining full backward compatibility.

Key Changes:
- New self-documenting JSON format with explicit roles (input/output/both)
- Automatic feature parsing and validation in update_config()
- Removed ~200+ lines of redundant code across examples
- Added comprehensive validation utilities

Core Components:
- hydragnn/utils/input_config_parsing/feature_config.py (NEW, 450+ lines)
  * parse_feature_config(): Handles both new and legacy formats
  * validate_feature_config(): Validates config against actual data
  * update_var_config_with_features(): Ensures backward compatibility
  * print_feature_summary(): Human-readable feature summary

- hydragnn/utils/input_config_parsing/config_utils.py (MODIFIED)
  * Integrated automatic feature parsing at start of update_config()

Examples Migrated (16 total):
- open_materials_2024, multidataset, omat24, open_catalyst_2020
- ani1_x, alexandria, transition1x, qcml, qm7x, open_catalyst_2022
- mptrj, multidataset_deepspeed, multibranch, multibranch_hpo
- dftb_uv_spectrum, open_molecules_2025

Test Configs Migrated (5 total):
- ci.json, ci_multihead.json, ci_vectoroutput.json
- ci_equivariant.json, ci_conv_head.json

Documentation:
- USER_MANUAL.md updated with comprehensive guide
- FEATURE_REFACTORING_PLAN.md, MIGRATION_GUIDE.md
- INTEGRATION_COMPLETE.md, MIGRATION_COMPLETE.md
- VALIDATION_REPORT.md

Testing:
✅ All validation tests passed (12/12 configs)
✅ Legacy format backward compatibility verified
✅ Code formatted with black==21.5b1
✅ No breaking changes

Benefits:
- Simpler, self-documenting configuration
- Automatic validation catches errors at config load time
- Single source of truth in JSON configs
- Better debugging with clear error messages
- Full backward compatibility maintained

Related: ORNL#392
@allaffa allaffa force-pushed the variables_of_interest_restructured branch from b3c0668 to cc0114f Compare November 4, 2025 17:19
@allaffa allaffa changed the title restructuring of handler for variables of interest completed restructuring of handler for variables of interest Nov 4, 2025
OpenEquivariance Integration and others added 16 commits November 4, 2025 12:25
- Remove unused var_config variable from qm9.py and zinc.py
- Add required feature configuration fields to JSON files (node_feature_names, node_feature_dims, graph_feature_names, graph_feature_dims)
- Fixes ValueError: Config must contain either 'node_features'/'graph_features' or 'node_feature_names'/'graph_feature_names'
- All example tests now passing (qm9, md17, zinc, LennardJones)
- Replace legacy format (separate lists) with new structured format
- Use node_features and graph_features dictionaries with explicit roles
- Makes configuration more self-documenting and maintainable
- New format: features defined with dim and role (input/output)
- All tests passing with new format
- Add update_var_config_with_features call at start of run_training to populate legacy keys
- Add redundant call in dataset_loading_and_splitting for safety
- Fix ci_multihead.json role assignments: only x has role 'both', x2/x3 are 'output' only
- Migrate qm9.json, md17.json, zinc.json to new self-documenting format
- Remove unused var_config variables from qm9.py and zinc.py

These changes fix KeyError: 'input_node_features' in 110+ tests and MACE multihead test failures.
All 68 graph tests now passing.
- Import update_var_config_with_features in test_model_loadpred.py
- Call it before get_log_name_config to populate input_node_features
- Call it again after loading saved config to ensure backward compatibility
- Fixes KeyError: 'input_node_features' in model load/predict test
@allaffa allaffa requested a review from erdemcaliskan November 5, 2025 18:07

@RylieWeaver RylieWeaver left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@allaffa

I agree that something like this could be very helpful to new users getting acclimated to HydraGNN. I noted some changes for documentation, but did not find any objectionable things that would cause errors.

Comment thread USER_MANUAL.md Outdated
Comment thread USER_MANUAL.md Outdated
Comment thread hydragnn/utils/input_config_parsing/feature_config.py
@allaffa allaffa requested a review from RylieWeaver November 5, 2025 22:53
OpenEquivariance Integration added 3 commits November 6, 2025 09:52
- New tests for edge cases and multi-dimensional node features
- Update pytest.ini to allow both test_ and pytest_ function prefixes
- Black formatting applied to new test script
return errors


def validate_node_feature_columns(data_x, node_feature_dims, column_indices=None):

@allaffa allaffa Nov 6, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RylieWeaver

I added this function to handle the edge case when (possibly) data.x contains more columns than the ones extracted. If you do not specify column_index for each feature, the number of columns in data.x must exactly match the sum of the dimensions of the node features specified in your configuration JSON, and the columns must be contiguous and in the order given. If you do specify column_index for each feature, data.x can have extra columns, and only the specified columns will be used for each feature.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@allaffa

Would you envision calling this before or after the atom features are extracted with extract_atom_features() in hydragnn/preprocess/graph_samples_checks_and_updates.py? Currently I only see it in tests and an import.

@allaffa allaffa Nov 7, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RylieWeaver
Thank you for your thorough revision!
I added a new commit where update_predicted_values now uses this check.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@allaffa

Nice! I think this will work, but consider putting it inside update_atom_features.

The reason being this check must be called before the node inputs are extracted and assigned to data.x, otherwise it will be checking for all the node features when only the node inputs are present. This has been done successfully so far because update_predicted_values is prior to update_atom_features everywhere I can find them both (serialized_dataloader, adiosdataset, distdataset, pickledataset), but it may be more robust to put it directly beforehand in the same function.

@allaffa allaffa requested a review from RylieWeaver November 6, 2025 15:16
OpenEquivariance Integration added 4 commits November 6, 2025 10:50
- Remove obsolete dataset names (CuAu_32atoms, FePt_32atoms, FeSi_1024atoms)
- Update configuration examples to use modern node_features/graph_features approach
- Replace legacy input_node_features/output_index documentation with modern approach
- Add comprehensive list of example datasets from examples/ directory
- Fix typos (HydgraGNN -> HydraGNN, plane -> plans)
- Add clearer references to USER_MANUAL.md throughout
- Reorganize configuration section for better clarity
- Update supported MPNN architectures list
- Note that legacy config format is still supported but not recommended
- Remove fixed-width integer formatting (:2d) from dim and index values
- This ensures output like 'dim=1' instead of 'dim= 1'
- Fixes test_print_feature_summary assertion that expects exact 'dim=1' tokens
return errors


def validate_node_feature_columns(data_x, node_feature_dims, column_indices=None):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@allaffa

Would you envision calling this before or after the atom features are extracted with extract_atom_features() in hydragnn/preprocess/graph_samples_checks_and_updates.py? Currently I only see it in tests and an import.


# Track input features
role = feat_config.get("role", "input")
if role in ["input", "both"]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@allaffa

Is there a scenario where we would want a feature to be both an input and an output?

@allaffa allaffa Nov 7, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RylieWeaver
Yes. There could be self-supervised learning (autoregressive) tasks where the same feature can be used as input and output. But I do also acknowledge that currently HydraGNN does not allow this use because update_predicted_values specifically extracts target nodal labels from data.x and moves them into data.y. For now, I removed this option in this commit

result["graph_feature_names"].append(feat_name)
result["graph_feature_dims"].append(feat_config["dim"])

# Graph features are typically outputs (unless role explicitly set)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be beneficial to remove 'typically' here. I don't think there are any ways for a graph feature to serve as an input with how the architecture is currently set up.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RylieWeaver
Thanks. I adjusted the comment in this commit

result["output_dim"].append(feat_config["dim"])
result["type"].append(feat_config.get("output_type", "node"))

node_idx += 1

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second look I think there may be something to fix here. I am not fully sure, so I will lay out my complete thinking so you can check it:

Assume we are using the automatically created config as you have added here
(1) Because of the for loop above, node_idx is appended to result["input_node_features"] for every node feature given in the config. So, for n provided features, then result["input_node_features"] = [0, 1, ..., n-1] no matter the dimensions of each of those node features.
(2) Later, in hydragnn/preprocess/serialized_dataset_loader.py, we have self.input_node_features = config["NeuralNetwork"]["Variables_of_interest"]["input_node_features"] --> update_atom_features(self.input_node_features, data)
(3) Then, in hydragnn/preprocess/graph_samples_check_and_updates.py, we have

def update_atom_features(atom_features: [AtomFeatures], data: Data):
    feature_indices = [i for i in atom_features]
    data.x = data.x[:, feature_indices]

In summary, I think this might be extracting only n features for n input node features, regardless of their respective dimensionalities (not what we want).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RylieWeaver
And you are correct here too.
I tried to address this situation in this commit

@allaffa allaffa force-pushed the variables_of_interest_restructured branch from cdf66d2 to 1301159 Compare November 7, 2025 19:17
@allaffa allaffa marked this pull request as draft November 10, 2025 18:28
@allaffa allaffa force-pushed the variables_of_interest_restructured branch from 52b9029 to 5b07b53 Compare November 19, 2025 13:23
@allaffa allaffa requested a review from RylieWeaver November 21, 2025 14:55
@allaffa

allaffa commented Nov 21, 2025

Copy link
Copy Markdown
Collaborator Author

@RylieWeaver
Thank you for feedback in your previous reviews.
I addressed the need to jump over multiple columns Indices when node features are multi-dimensional.
cf0de42
The CI test are failing for small fluctuations of the error.

@erdemcaliskan
any feedback from you would also be highly appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants