From caa15407cdec32a33ac56c356eeae096c989f8ce Mon Sep 17 00:00:00 2001 From: Suyash Date: Sun, 20 Jul 2025 20:52:34 +0530 Subject: updating the converter's functionality and adding read() function --- converter/README.md | 333 +++++--- converter/context_case_to_trees.md | 192 ----- converter/context_trees_to_case.md | 237 ------ converter/converter.py | 453 ----------- converter/pyvnt_package/context_case_to_trees.md | 192 +++++ converter/pyvnt_package/context_trees_to_case.md | 237 ++++++ converter/pyvnt_package/converter.py | 861 +++++++++++++++++++++ .../converted_fvSchemes_20250720_093827.py | 86 ++ .../converted_fvSchemes_20250720_094230.py | 65 ++ .../converted_fvSolution_20250720_095416.py | 50 ++ converter/pyvnt_package/test.py | 6 + 11 files changed, 1723 insertions(+), 989 deletions(-) delete mode 100644 converter/context_case_to_trees.md delete mode 100644 converter/context_trees_to_case.md delete mode 100644 converter/converter.py create mode 100644 converter/pyvnt_package/context_case_to_trees.md create mode 100644 converter/pyvnt_package/context_trees_to_case.md create mode 100644 converter/pyvnt_package/converter.py create mode 100644 converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSchemes_20250720_093827.py create mode 100644 converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSchemes_20250720_094230.py create mode 100644 converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSolution_20250720_095416.py create mode 100644 converter/pyvnt_package/test.py diff --git a/converter/README.md b/converter/README.md index b16dab7..9d90e9f 100644 --- a/converter/README.md +++ b/converter/README.md @@ -1,28 +1,31 @@ -# OpenFOAM ↔ pyvnt Bidirectional Converter +# OpenFOAM pyvnt Bidirectional Converter An AI-powered tool for seamless conversion between OpenFOAM case files and pyvnt Python code structures. This converter uses Google's Gemini AI to intelligently translate between these two formats with high accuracy and proper formatting. ## Table of Contents -- [Features](#-features) -- [Prerequisites](#-prerequisites) -- [Installation](#-installation) -- [Configuration](#-configuration) -- [Usage](#-usage) -- [File Structure](#-file-structure) -- [Examples](#-examples) -- [Troubleshooting](#-troubleshooting) -- [Contributing](#-contributing) +- [Features](#features) +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Configuration](#configuration) +- [Usage](#usage) +- [API Reference](#api-reference) +- [File Structure](#file-structure) +- [Examples](#examples) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) ## Features -- **šŸ”„ Bidirectional Conversion**: Convert OpenFOAM case files to pyvnt Python code and vice versa -- **šŸ¤– AI-Powered**: Uses Google Gemini 2.0 Flash for intelligent, context-aware conversions -- **šŸ“ Organized Output**: Automatically saves Python files to `pyvnt_package/` directory -- **šŸ’» Interactive CLI**: User-friendly command-line interface with animated loading indicators -- **šŸŽÆ Context-Aware**: Supports external context files for improved conversion accuracy -- **šŸ›”ļø Error Handling**: Comprehensive error handling with graceful fallbacks -- **šŸ“ Automatic Formatting**: Proper code formatting and structure validation +- **Bidirectional Conversion**: Convert OpenFOAM case files to pyvnt Python code and vice versa +- **AI-Powered**: Uses Google Gemini 2.0 Flash for intelligent, context-aware conversions +- **Programmatic API**: `read()` function for direct integration into Python scripts +- **Module Management**: Save and load converted structures as reusable Python modules +- **Organized Output**: Automatically creates structured directory hierarchy +- **Interactive CLI**: User-friendly command-line interface with animated loading indicators +- **Context-Aware**: Supports external context files for improved conversion accuracy +- **Code Validation**: Automatic syntax and structure validation of generated code +- **Direct Execution**: Execute generated code directly in isolated namespace ## Prerequisites @@ -34,6 +37,7 @@ Before running the converter, ensure you have: ```bash pip install google-generativeai pathlib ``` +4. **pyvnt library** installed and accessible ## Installation @@ -47,6 +51,8 @@ Before running the converter, ensure you have: export GEMINI_API_KEY="your_api_key_here" ``` +3. **Ensure pyvnt is available** in your Python environment + ## Configuration ### Getting Your Gemini API Key @@ -70,85 +76,169 @@ Before running the converter, ensure you have: For enhanced conversion accuracy, you can create context files: -- `context_case_to_trees.md` - For OpenFOAM → pyvnt conversion -- `context_trees_to_case.md` - For pyvnt → OpenFOAM conversion +- `context_case_to_trees.md` - For OpenFOAM to pyvnt conversion +- `context_trees_to_case.md` - For pyvnt to OpenFOAM conversion Place these files in the same directory as the converter script. ## Usage -### Running the Converter +### Interactive Mode ```bash python3 converter.py ``` -### Interactive Menu +The converter presents four options: + +1. **OpenFOAM Case Files to pyvnt Python Code** +2. **pyvnt Python Code to OpenFOAM Case Files** +3. **Use read() function (programmatic demonstration)** +4. **Exit** + +### Programmatic Usage + +#### Basic File Conversion + +```python +from converter import read -The converter will present you with three options: +# Convert OpenFOAM case file to pyvnt tree +root_tree = read('path/to/your/case_file.txt') +# The root_tree is now a pyvnt Node_C object ready for use +print(f"Root node: {root_tree.name}") ``` -šŸš€ BIDIRECTIONAL CONVERTER: OpenFOAM ↔ pyvnt (AI-Powered) -====================================================================== -Select conversion mode: -1. šŸ“„ OpenFOAM Case Files → šŸ pyvnt Python Code -2. šŸ pyvnt Python Code → šŸ“„ OpenFOAM Case Files -3. 🚪 Exit +#### Advanced Options + +```python +# Convert with module saving +root_tree = read( + 'fvSolution', + save_module=True, + module_name='my_custom_solver_config', + verbose=True, + use_tree_modules=True +) + +# Load a previously saved module +from converter import load_saved_module +root_tree = load_saved_module('my_custom_solver_config', from_tree_modules=True) + +# List available saved modules +from converter import list_saved_modules +modules = list_saved_modules(from_tree_modules=True) +print("Available modules:", modules) ``` -### Step-by-Step Process +## API Reference + +### read(file_path, save_module=True, module_name=None, verbose=False, use_tree_modules=True) -#### Option 1: OpenFOAM → pyvnt +Convert an OpenFOAM case file to a pyvnt tree structure. -1. **Select Option 1** -2. **Paste your OpenFOAM case file content** when prompted -3. **Type 'END'** on a new line to finish input -4. **Wait for AI processing** (animated loading indicator will show) -5. **Review the generated pyvnt code** -6. **Choose to save** the result to a file -7. **Provide a filename** or use the default timestamp-based name -8. **File saved** to `pyvnt_package/` directory +**Parameters:** +- `file_path` (str): Path to the OpenFOAM case file +- `save_module` (bool): Whether to save the generated code as a reusable module +- `module_name` (str): Custom name for the saved module (optional) +- `verbose` (bool): Whether to print detailed progress information +- `use_tree_modules` (bool): Whether to save modules in tree_modules/ directory -#### Option 2: pyvnt → OpenFOAM +**Returns:** +- `Node_C`: The root node of the constructed pyvnt tree -1. **Select Option 2** -2. **Paste your pyvnt Python code** when prompted -3. **Type 'END'** on a new line to finish input -4. **Wait for AI processing** -5. **Review the generated OpenFOAM case file** -6. **Choose to save** the result to a file -7. **File saved** to `generated_text_files/` directory +**Raises:** +- `FileNotFoundError`: If the input file doesn't exist +- `RuntimeError`: If conversion or execution fails -### Input Methods +### load_saved_module(module_name, from_tree_modules=True, verbose=False) -You can provide input in two ways: +Load a previously saved module and return its root tree. -1. **Interactive paste**: Copy and paste your content, then type 'END' -2. **EOF signal**: Use Ctrl+D (Linux/Mac) or Ctrl+Z+Enter (Windows) to finish +**Parameters:** +- `module_name` (str): Name of the module to load (without .py extension) +- `from_tree_modules` (bool): Whether to load from tree_modules/ or pyvnt_package/ +- `verbose` (bool): Whether to print progress information + +**Returns:** +- `Node_C`: The root node of the loaded pyvnt tree + +### list_saved_modules(from_tree_modules=True) + +List all saved modules in the specified directory. + +**Parameters:** +- `from_tree_modules` (bool): Whether to list from tree_modules/ or pyvnt_package/ + +**Returns:** +- `list`: List of module names (without .py extension) ## File Structure -After running the converter, your directory structure will look like: +After running the converter, your directory structure will be: ``` -your_project/ -ā”œā”€ā”€ converter.py # Main converter script -ā”œā”€ā”€ context_case_to_trees.md # Optional context file -ā”œā”€ā”€ context_trees_to_case.md # Optional context file -ā”œā”€ā”€ pyvnt_package/ # Generated Python files -│ ā”œā”€ā”€ pyvnt_code_20240115_143022.py -│ └── custom_filename.py -└── generated_text_files/ # Generated OpenFOAM files - ā”œā”€ā”€ openfoam_case_20240115_143155.txt - └── custom_case.txt +converter/pyvnt_package/ +ā”œā”€ā”€ converter.py # Main converter script +ā”œā”€ā”€ context_case_to_trees.md # Optional context file +ā”œā”€ā”€ context_trees_to_case.md # Optional context file +└── converter/ # Generated output directory + ā”œā”€ā”€ generated_text_files/ # Generated OpenFOAM files + │ ā”œā”€ā”€ openfoam_case_20240115_143155.txt + │ └── custom_case.txt + └── pyvnt_package/ # Generated Python files + ā”œā”€ā”€ pyvnt_code_20240115_143022.py + ā”œā”€ā”€ custom_filename.py + └── tree_modules/ # Saved module directory + ā”œā”€ā”€ converted_fvSolution_20240115_143022.py + └── my_custom_solver_config.py ``` ## Examples +### Basic Programmatic Usage + +```python +from converter import read + +# Convert a simple case file +try: + root = read('system/fvSolution', verbose=True) + print(f"Successfully converted! Root node: {root.name}") + + # Access the tree structure + for child in root.children: + print(f"Child node: {child.name}") + +except FileNotFoundError: + print("File not found") +except RuntimeError as e: + print(f"Conversion failed: {e}") +``` + +### Working with Saved Modules + +```python +from converter import read, load_saved_module, list_saved_modules + +# Save a conversion as a reusable module +root = read('controlDict', + save_module=True, + module_name='control_dict_config', + use_tree_modules=True) + +# Later, load the saved module +loaded_root = load_saved_module('control_dict_config') + +# List all available modules +available = list_saved_modules() +print("Saved modules:", available) +``` + ### OpenFOAM Case File Example -**Input:** +**Input (fvSolution):** ``` solvers { @@ -168,19 +258,27 @@ solvers relTol 0.1; } } + +PISO +{ + nCorrectors 2; + nNonOrthogonalCorrectors 0; + pRefCell 0; + pRefValue 0; +} ``` **Generated pyvnt Code:** ```python -from pyvnt import Node_C, Key_C, Int_P, Flt_P, Str_P, Enm_P, show_tree +from pyvnt import * -# Root node +# Create root node root = Node_C('root', None, None) -# Solvers node +# Solvers configuration solvers = Node_C('solvers', root, None) -# Pressure solver configuration +# Pressure solver p = Node_C('p', solvers, None, Key_C('solver', Enm_P('solver', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')), Key_C('preconditioner', Enm_P('preconditioner', items={'DIC', 'DILU', 'diagonal'}, default='DIC')), @@ -188,78 +286,99 @@ p = Node_C('p', solvers, None, Key_C('relTol', Flt_P('relTol', min_val=0.0, max_val=1.0, default=0.05)) ) -# Velocity solver configuration +# Velocity solver U = Node_C('U', solvers, None, - Key_C('solver', Enm_P('solver', items={'smoothSolver', 'PBiCG', 'PBiCGStab'}, default='smoothSolver')), - Key_C('smoother', Enm_P('smoother', items={'symGaussSeidel', 'GaussSeidel', 'DIC'}, default='symGaussSeidel')), + Key_C('solver', Enm_P('solver', items={'smoothSolver', 'PBiCG'}, default='smoothSolver')), + Key_C('smoother', Enm_P('smoother', items={'symGaussSeidel', 'GaussSeidel'}, default='symGaussSeidel')), Key_C('tolerance', Flt_P('tolerance', min_val=1e-12, max_val=1e-1, default=1e-05)), Key_C('relTol', Flt_P('relTol', min_val=0.0, max_val=1.0, default=0.1)) ) -# Display the tree structure +# PISO algorithm settings +PISO = Node_C('PISO', root, None, + Key_C('nCorrectors', Int_P('nCorrectors', min_val=1, max_val=10, default=2)), + Key_C('nNonOrthogonalCorrectors', Int_P('nNonOrthogonalCorrectors', min_val=0, max_val=5, default=0)), + Key_C('pRefCell', Int_P('pRefCell', min_val=0, default=0)), + Key_C('pRefValue', Flt_P('pRefValue', default=0.0)) +) + +# Display the tree show_tree(root) ``` -### pyvnt to OpenFOAM Conversion - -The above pyvnt code would convert back to properly formatted OpenFOAM case file format. - -## šŸ”§ Troubleshooting +## Troubleshooting ### Common Issues -#### 1. API Key Error +#### API Key Error ``` Error: GEMINI_API_KEY environment variable not set ``` **Solution**: Set your Gemini API key as an environment variable. -#### 2. Network/API Errors +#### File Not Found ``` -āŒ Error generating code: [API Error] +FileNotFoundError: File not found: path/to/file ``` -**Solution**: -- Check your internet connection -- Verify your API key is valid -- Ensure you haven't exceeded API rate limits +**Solution**: Verify the file path exists and is accessible. -#### 3. Directory Creation Errors +#### Conversion Failures ``` -āŒ Error creating directory: [Permission Error] +RuntimeError: Failed to execute generated code ``` **Solution**: -- Ensure you have write permissions in the current directory -- Run the script from a directory where you have full access +- Ensure pyvnt library is properly installed +- Check that the input OpenFOAM file has valid syntax +- Try with verbose=True to see detailed error information -#### 4. Invalid Input Format +#### Module Loading Issues +``` +ModuleNotFoundError: No module named 'pyvnt' +``` **Solution**: -- Ensure your OpenFOAM case file has proper syntax -- For pyvnt code, verify it follows the correct class structure +- Ensure pyvnt is installed in your Python environment +- Check that the pyvnt_package directory is in the correct location + +### Code Validation + +The converter includes automatic validation: +- Syntax checking of generated Python code +- Verification of required imports and function calls +- Structure validation for pyvnt compatibility ### Getting Help If you encounter issues: -1. **Check the error message** - most issues are clearly indicated -2. **Verify your API key** - ensure it's correctly set and valid -3. **Check file permissions** - ensure you can write to the current directory -4. **Validate input format** - ensure your input follows the expected structure +1. **Use verbose mode**: Set `verbose=True` to see detailed progress +2. **Check file permissions**: Ensure you can read input files and write to output directories +3. **Validate input format**: Ensure your OpenFOAM files follow proper dictionary syntax +4. **Verify API connectivity**: Check your internet connection and API key validity + +## Tips for Best Results -## šŸ“‹ Tips for Best Results +### For OpenFOAM to pyvnt Conversion -### For OpenFOAM → pyvnt Conversion +- Use complete case file sections rather than fragments +- Ensure proper OpenFOAM dictionary syntax with braces and semicolons +- Include proper indentation in your input files +- Use standard OpenFOAM keywords and values when possible -- **Use complete case file sections** rather than fragments -- **Include proper OpenFOAM dictionary syntax** with braces and semicolons -- **Ensure proper indentation** in your input +### For pyvnt to OpenFOAM Conversion -### For pyvnt → OpenFOAM Conversion +- Use complete pyvnt tree structures with proper Node_C hierarchy +- Include all necessary imports at the top of your code +- Follow pyvnt naming conventions and class structure +- Ensure all Key_C objects contain valid ValueProperty instances -- **Use complete pyvnt tree structures** with proper Node_C hierarchy -- **Include all necessary imports** at the top of your code -- **Follow pyvnt naming conventions** and class structure +### Module Management -## šŸ¤ Contributing +- Use descriptive names for saved modules +- Organize modules in the tree_modules directory for better structure +- Regularly clean up unused modules to maintain organization +- Use the list_saved_modules() function to track available conversions + +## Contributing This converter is designed to be extensible. You can: @@ -267,9 +386,9 @@ This converter is designed to be extensible. You can: 2. **Modify the embedded contexts** for better conversion accuracy 3. **Extend the property types** supported by the converter 4. **Add new output formats** or directory structures +5. **Enhance the module management system** +6. **Improve error handling and validation** -## šŸ“„ License - -This tool is provided as-is for educational and research purposes. Please ensure you comply with Google's Gemini API terms of service when using this converter. +## License ---- +This tool is provided as-is for educational and research purposes. Please ensure you comply with Google's Gemini API terms of service when using this converter. \ No newline at end of file diff --git a/converter/context_case_to_trees.md b/converter/context_case_to_trees.md deleted file mode 100644 index 2bc5d13..0000000 --- a/converter/context_case_to_trees.md +++ /dev/null @@ -1,192 +0,0 @@ -# pyvnt: Class Structure and Example Usage Context - -## Core Class Structure - -### 1. ValueProperty (Abstract) -- Parent for all property classes (e.g., `Int_P`, `Flt_P`, `Enm_P`). -- Enforces interface for value properties used in Key_C. - -### 2. Int_P, Flt_P, Str_P, Enm_P -- Store typed values with constraints (e.g., min/max/default for ints/floats, set of choices for enums). -- Example: `Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')` - -### 3. Key_C -- Represents a dictionary-like node holding multiple ValueProperty instances. -- Enforces encapsulation: attributes are only set via constructor or methods. -- Example: `Key_C('solver', prop1, prop2)` -- Methods: `replaceVal`, `delVal`, `giveVal`, etc. - -### 4. Node_C -- Tree node class (inherits from anytree's NodeMixin). -- Holds a name, parent, children, and a list of Key_C objects as data. -- Methods: `addChild`, `setParent`, `add_data`, `removeData`, `getChild`, etc. - -### 5. show_tree -- Utility to print the tree structure and Key_C at each node. - -## Example: Creating and Displaying a Tree - -```python -from pyvnt import * - -# Define properties -prop1 = Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG') -prop2 = Enm_P('val2', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PBiCG') - -# Create Key_C -key1 = Key_C('solver', prop1, prop2) - -# Build tree -head = Node_C("test_head", None, None) -child1 = Node_C('test_child', head, None) -child2 = Node_C('test_child2', child1, None, key1) -child3 = Node_C('test_child3', child1, None, key1) - -# Display tree -show_tree(head) -``` - -## Example Output (Tree Structure) -``` -test_head -|--test_child -| |--test_child2 -| | { -| | solver : PCG, PBiCG, -| | } -| |--test_child3 -| | { -| | solver : PCG, PBiCG, -| | } -``` - -## Complete Example: - -```python -from pyvnt import * - -head = Node_C('fvSolutions') -sl = Node_C('solvers', parent = head) - -s = Key_C('solver', Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')) -pc = Key_C('preconditioner', Enm_P('val1', items={'DIC', 'DILU', 'FDIC'}, default='DIC')) -tol = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1000, default=1e-06)) -rt = Key_C('relTol', Flt_P('val1', minimum=0, maximum=100, default=0.05)) - -p = Node_C('p', sl, None, pc, s, tol, rt) -relTol2 = Key_C('relTol', Flt_P('val1', minimum=0, maximum=100, default=0)) -pf = Node_C('pFinal', sl, None, relTol2) - -sol2 = Key_C('solver', Enm_P('val1', items={'smoothSolver'}, default='smoothSolver')) -sm = Key_C('smoother', Enm_P('val1', items={'symGaussSeidel', 'gaussSeidel'}, default = 'symGaussSeidel')) -tol2 = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1000, default=1e-05)) -relTol3 = Key_C('relTol', Flt_P('val1', minimum=0, maximum=100, default=0)) - -u = Node_C('U', sl, None, sol2, sm, tol2, relTol3) - -ncorr = Key_C('nCorrectors', Int_P('int_prop_1', minimum=0, maximum=100, default=2)) -nnoc = Key_C('nNonOrthogonalCorrectors', Int_P('int_prop_2', minimum=0, maximum=100, default=0)) -prc = Key_C('pRefCell', Int_P('int_prop_3', minimum=0, maximum=100, default=0)) -prv = Key_C('pRefValue', Int_P('int_prop_4', minimum=0, maximum=100, default=0)) - -piso = Node_C('PISO', head, None, ncorr, nnoc, prc, prv) - -show_tree(head) -``` - -### Output -``` -fvSolutions -ā”œā”€ā”€ solvers -│ ā”œā”€ā”€ p -│ │ { -│ │ preconditioner : DIC, -│ │ solver : PCG, -│ │ tolerance : 1e-06, -│ │ relTol : 0.05, -│ │ } -│ ā”œā”€ā”€ pFinal -│ │ { -│ │ relTol : 0, -│ │ } -│ └── U -│ { -│ solver : smoothSolver, -│ smoother : symGaussSeidel, -│ tolerance : 1e-05, -│ relTol : 0, -│ } -└── PISO - { - nCorrectors : 2, - nNonOrthogonalCorrectors : 0, - pRefCell : 0, - pRefValue : 0, - } -``` - -## Important Note on Node_C Construction and Leaf Node Errors - -**Common mistake:** -The way you call the `Node_C` constructor determines whether a node is treated as a leaf (cannot have children) or not. - -### Example from `testfile.py` (Correct) -```python -p = Node_C('p', sl, None, pc, s, tol, rt) -``` -- `'p'` is the node name. -- `sl` is the parent node. -- `None` is explicitly passed as the third argument (children). -- The rest (`pc, s, tol, rt`) are data objects. - -**Why this works:** -Passing `None` for the children argument tells the `Node_C` class that this node is **not a leaf** and can have children added later. The remaining arguments are treated as data. - ---- - -### Example from `main.py` (Potential Issue) -```python -solvers_node = Node_C("solvers", parent=fv_solution_root) -``` -- Here, only the `parent` is specified using a keyword argument. -- If the constructor does not receive the children argument (or receives it as something other than `None` or a list), it may **default to making the node a leaf**. - ---- - -### Why the difference? -- The `Node_C` constructor likely checks if the third argument (children) is `None` or a list. - - If `None`, the node can have children added later. - - If omitted or passed incorrectly, it might default to a leaf node. -- This is why in `testfile.py`, the node is not a leaf, but in `main.py`, it may be. - ---- - -### Recommendation - -**Always pass `None` as the third argument to the `Node_C` constructor if you want the node to be able to have children later.** -This matches the intended usage and avoids the `LeafNodeError`. - -**Example fix for main.py:** -```python -solvers_node = Node_C("solvers", fv_solution_root, None) -``` - -**IMPORTANT** -add_data() only takes 3 positional arguments, never give more than 3. - -## Class Name Mapping (Old vs New Syntax) - -| Old Syntax | New Syntax | -|------------|------------| -| `Foam` | `Node_C` | -| `KeyData` | `Key_C` | -| `PropertyInt` | `Int_P` | -| `PropertyFloat` | `Flt_P` | -| `PropertyString` | `Str_P` | -| `EnumProp` | `Enm_P` | -| `showTree` | `show_tree` | - -**Summary:** -- In `testfile.py`, passing `None` for children tells `Node_C` the node is not a leaf. -- In `main.py`, omitting the children argument may cause the node to be a leaf. -- **Check the `Node_C` class constructor signature and always follow the correct argument order and usage.** \ No newline at end of file diff --git a/converter/context_trees_to_case.md b/converter/context_trees_to_case.md deleted file mode 100644 index a05f892..0000000 --- a/converter/context_trees_to_case.md +++ /dev/null @@ -1,237 +0,0 @@ -# pyvnt to OpenFOAM Case File Converter Context - -## Overview -This context describes how to convert pyvnt tree structures back to OpenFOAM case file format. The conversion process involves traversing the Node_C tree structure and generating properly formatted OpenFOAM dictionary syntax. - -## OpenFOAM Dictionary Format Rules - -### 1. Basic Structure -``` -dictionaryName -{ - key1 value1; - key2 value2; - - subDict1 - { - nestedKey1 nestedValue1; - nestedKey2 nestedValue2; - } - - subDict2 - { - // more nested content - } -} -``` - -### 2. Formatting Rules -- Dictionary names are followed by opening brace `{` on the same line or next line -- Key-value pairs are separated by whitespace and terminated with semicolon `;` -- Nested dictionaries follow the same pattern -- Proper indentation (typically 4 spaces per level) -- Comments can be added with `//` or `/* */` - -### 3. Value Types in OpenFOAM -- **Strings**: Can be quoted or unquoted (e.g., `PCG` or `"PCG"`) -- **Numbers**: Integer or floating point (e.g., `1`, `1.0`, `1e-06`) -- **Booleans**: `true`, `false`, `yes`, `no`, `on`, `off` -- **Lists**: `(value1 value2 value3)` or `[value1 value2 value3]` -- **Vectors**: `(x y z)` - -## pyvnt to OpenFOAM Conversion Logic - -### 1. Tree Traversal -- Start from root Node_C -- Recursively traverse all children -- Convert each Node_C to OpenFOAM dictionary format - -### 2. Node_C Conversion -Each Node_C becomes an OpenFOAM dictionary: -```python -# pyvnt Node_C with name "solvers" -solvers_node = Node_C("solvers", parent, None, key1, key2) - -# Converts to OpenFOAM: -solvers -{ - key1_name key1_value; - key2_name key2_value; -} -``` - -### 3. Key_C Conversion -Each Key_C becomes a key-value pair: -```python -# pyvnt Key_C -tolerance = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1000, default=1e-06)) - -# Converts to OpenFOAM: -tolerance 1e-06; -``` - -### 4. Property Value Extraction -- **Int_P**: Extract integer value -- **Flt_P**: Extract float value (format appropriately, e.g., scientific notation) -- **Str_P**: Extract string value -- **Enm_P**: Extract selected enum value - -## Example Conversion - -### Input pyvnt Tree: -```python -from pyvnt import * - -head = Node_C('fvSolutions') -sl = Node_C('solvers', parent=head) - -s = Key_C('solver', Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')) -tol = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1000, default=1e-06)) -p = Node_C('p', sl, None, s, tol) - -ncorr = Key_C('nCorrectors', Int_P('int_prop_1', minimum=0, maximum=100, default=2)) -piso = Node_C('PISO', head, None, ncorr) -``` - -### Output OpenFOAM Format: -``` -fvSolutions -{ - solvers - { - p - { - solver PCG; - tolerance 1e-06; - } - } - - PISO - { - nCorrectors 2; - } -} -``` - -## Implementation Guidelines - -### 1. Recursive Tree Traversal -```python -def convert_node_to_openfoam(node, indent_level=0): - indent = " " * indent_level - lines = [] - - # Add node name and opening brace - lines.append(f"{indent}{node.name}") - lines.append(f"{indent}{{") - - # Convert Key_C objects to key-value pairs - for key_data in node.data: - key_line = convert_key_to_openfoam(key_data, indent_level + 1) - lines.append(key_line) - - # Recursively convert children - for child in node.children: - child_lines = convert_node_to_openfoam(child, indent_level + 1) - lines.extend(child_lines) - - # Add closing brace - lines.append(f"{indent}}}") - - return lines -``` - -### 2. Key_C to OpenFOAM Conversion -```python -def convert_key_to_openfoam(key_data, indent_level=0): - indent = " " * indent_level - - # Extract value from the ValueProperty - value = extract_property_value(key_data) - - # Format based on value type - formatted_value = format_openfoam_value(value) - - return f"{indent}{key_data.name} {formatted_value};" -``` - -### 3. Value Formatting -```python -def format_openfoam_value(value): - if isinstance(value, str): - return value - elif isinstance(value, int): - return str(value) - elif isinstance(value, float): - if value == 0: - return "0" - elif abs(value) < 1e-4 or abs(value) > 1e4: - return f"{value:.6e}" - else: - return str(value) - else: - return str(value) -``` - -## Error Handling and Edge Cases - -### 1. Empty Nodes -- Nodes with no data and no children should still generate dictionary structure -- Add comment indicating empty dictionary if needed - -### 2. Special Characters -- Handle special characters in names and values -- Quote strings if they contain spaces or special characters - -### 3. File Headers -- Add appropriate OpenFOAM file headers (FoamFile dictionary) -- Include version, format, class, object, etc. - -### 4. Value Validation -- Ensure extracted values are valid OpenFOAM syntax -- Handle edge cases like infinity, NaN, etc. - -## Complete Example Function Structure - -```python -def pyvnt_to_openfoam(root_node, add_foam_header=True): - """ - Convert pyvnt tree to OpenFOAM case file format - - Args: - root_node: Root Node_C of the pyvnt tree - add_foam_header: Whether to add FoamFile header - - Returns: - str: OpenFOAM formatted case file content - """ - lines = [] - - if add_foam_header: - lines.extend(generate_foam_header(root_node.name)) - - # Convert the tree - tree_lines = convert_node_to_openfoam(root_node) - lines.extend(tree_lines) - - return '\n'.join(lines) -``` - -## Usage Notes - -### 1. Accessing Node Data -- Use `node.data` to access list of Key_C objects -- Use `node.children` to access child nodes -- Use `node.name` for the dictionary name - -### 2. Extracting Property Values -- Each Key_C contains ValueProperty objects -- Use appropriate methods to extract current values -- Handle default values appropriately - -### 3. Indentation and Formatting -- Maintain consistent 4-space indentation -- Align values for readability -- Add blank lines between major sections - -This context provides the foundation for implementing the pyvnt to OpenFOAM conversion functionality. \ No newline at end of file diff --git a/converter/converter.py b/converter/converter.py deleted file mode 100644 index 29530ae..0000000 --- a/converter/converter.py +++ /dev/null @@ -1,453 +0,0 @@ -#!/usr/bin/env python3 -""" -Improved Bidirectional Converter: Case Files ↔ pyvnt Python Code -Both directions now use Gemini LLM for reliable conversion! -""" -import os -import sys -import ast -import traceback -import re -import threading -import time -import google.generativeai as genai -from pathlib import Path -from datetime import datetime - -class LoadingIndicator: - """A simple loading indicator with animated dots""" - def __init__(self, message="Loading"): - self.message = message - self.is_running = False - self.thread = None - - def start(self): - """Start the loading indicator""" - self.is_running = True - self.thread = threading.Thread(target=self._animate) - self.thread.daemon = True - self.thread.start() - - def stop(self): - """Stop the loading indicator""" - self.is_running = False - if self.thread: - self.thread.join() - # Clear the line - print("\r" + " " * (len(self.message) + 10), end="\r") - - def _animate(self): - """Animate the loading dots""" - dots = 0 - while self.is_running: - dots_str = "." * (dots % 4) - print(f"\r{self.message}{dots_str:<3}", end="", flush=True) - time.sleep(0.5) - dots += 1 - -class ConversionMode: - CASE_TO_PYVNT = "case_to_pyvnt" - PYVNT_TO_CASE = "pyvnt_to_case" - -def create_output_directories(): - """Create output directories if they don't exist""" - directories = { - 'python': Path("pyvnt_package"), # Changed from "generated_python_files" - 'text': Path("generated_text_files") - } - - for dir_type, directory in directories.items(): - try: - directory.mkdir(exist_ok=True) - print(f"āœ… Directory ready: {directory}") - except Exception as e: - print(f"āŒ Error creating directory {directory}: {e}") - sys.exit(1) - - return directories - -def get_output_filename(mode, custom_name=None): - """Generate appropriate filename with timestamp""" - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - - if custom_name: - # Use custom name but ensure correct extension - if mode == ConversionMode.CASE_TO_PYVNT: - if not custom_name.endswith('.py'): - custom_name += '.py' - else: - if not custom_name.endswith('.txt'): - custom_name += '.txt' - return custom_name - - # Generate default filename with timestamp - if mode == ConversionMode.CASE_TO_PYVNT: - return f"pyvnt_code_{timestamp}.py" - else: - return f"openfoam_case_{timestamp}.txt" - -def load_context(mode): - """Load the appropriate context based on conversion mode""" - if mode == ConversionMode.CASE_TO_PYVNT: - context_file = Path("context_case_to_trees.md") - fallback_context = get_case_to_pyvnt_context() - else: - # Use your specific context file name - context_file = Path("context_trees_to_case.md") - fallback_context = get_pyvnt_to_openfoam_context() - - if not context_file.exists(): - print(f"Warning: {context_file} not found. Using embedded context.") - return fallback_context - - try: - with open(context_file, 'r', encoding='utf-8') as f: - context_content = f.read() - print(f"āœ“ Loaded context from {context_file}") - return context_content - except Exception as e: - print(f"Error reading {context_file}: {e}") - return fallback_context - -def get_case_to_pyvnt_context(): - """Embedded context for case file to pyvnt conversion""" - return """ -# pyvnt: Class Structure and Example Usage Context - -## Core Class Structure - -### 1. ValueProperty (Abstract) -- Parent for all property classes (e.g., `Int_P`, `Flt_P`, `Enm_P`). -- Enforces interface for value properties used in Key_C. - -### 2. Int_P, Flt_P, Str_P, Enm_P -- Store typed values with constraints (e.g., min/max/default for ints/floats, set of choices for enums). -- Example: `Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')` - -### 3. Key_C -- Represents a dictionary-like node holding multiple ValueProperty instances. -- Enforces encapsulation: attributes are only set via constructor or methods. -- Example: `Key_C('solver', prop1, prop2)` - -### 4. Node_C -- Tree node class (inherits from anytree's NodeMixin). -- Holds a name, parent, children, and a list of Key_C objects as data. -- Always pass `None` as the third argument for non-leaf nodes. -- Example: `Node_C('nodeName', parent, None, data1, data2)` - -### 5. show_tree -- Utility to print the tree structure and Key_C at each node. -""" - -def get_pyvnt_to_openfoam_context(): - """Enhanced embedded context for pyvnt to OpenFOAM conversion""" - return """ -# pyvnt to OpenFOAM Case File Converter Context - -You are an expert in converting pyvnt Python tree structures to OpenFOAM case file format. - -## Key Conversion Rules: - -1. **Node_C Structure**: Each Node_C represents a dictionary/subdictionary in OpenFOAM format -2. **Key_C to Properties**: Each Key_C contains ValueProperty instances that become key-value pairs -3. **Property Value Extraction**: Extract actual values from .value or .default attributes -4. **OpenFOAM Formatting**: Use proper dictionary syntax with braces, semicolons, and 4-space indentation - -## Conversion Process: -1. Traverse the Node_C tree recursively -2. For each Node_C: create OpenFOAM dictionary with proper braces -3. For each Key_C in node.data: extract property values and format as key-value pairs -4. Maintain hierarchical structure through proper nesting - -## Example: -```python -# pyvnt structure -p_node = Node_C('p', parent, None, - Key_C('solver', Enm_P('val1', default='PCG')), - Key_C('tolerance', Flt_P('val1', default=1e-06)) -) - -# OpenFOAM output -p -{ - solver PCG; - tolerance 1e-06; -} -``` - -Extract ALL property values from every Key_C object in the tree structure. -""" - -def setup_gemini(): - """Setup Gemini API client""" - api_key = os.getenv('GEMINI_API_KEY') - if not api_key: - print("Error: GEMINI_API_KEY environment variable not set") - print("Please set your Gemini API key: export GEMINI_API_KEY=your_api_key_here") - sys.exit(1) - - genai.configure(api_key=api_key) - model = genai.GenerativeModel('gemini-2.0-flash') - return model - -def get_conversion_mode(): - """Get conversion mode from user""" - print("=" * 70) - print("šŸš€ BIDIRECTIONAL CONVERTER: OpenFOAM ↔ pyvnt (AI-Powered)") - print("=" * 70) - print("\nSelect conversion mode:") - print("1. šŸ“„ OpenFOAM Case Files → šŸ pyvnt Python Code") - print("2. šŸ pyvnt Python Code → šŸ“„ OpenFOAM Case Files") - print("3. 🚪 Exit") - - while True: - try: - choice = input("\nEnter your choice (1-3): ").strip() - if choice == "1": - return ConversionMode.CASE_TO_PYVNT - elif choice == "2": - return ConversionMode.PYVNT_TO_CASE - elif choice == "3": - print("šŸ‘‹ Goodbye!") - sys.exit(0) - else: - print("āŒ Invalid choice. Please enter 1, 2, or 3.") - except KeyboardInterrupt: - print("\n\nāš ļø Operation cancelled.") - sys.exit(0) - except EOFError: - print("\n\nāš ļø Operation cancelled.") - sys.exit(0) - -def get_input_content(mode): - """Get input content based on conversion mode""" - if mode == ConversionMode.CASE_TO_PYVNT: - print("\n" + "=" * 60) - print("šŸ“„ CASE FILE TO PYVNT CONVERTER") - print("=" * 60) - print("\nšŸ“ Please paste your OpenFOAM case file content below.") - else: - print("\n" + "=" * 60) - print("šŸ PYVNT TO CASE FILE CONVERTER") - print("=" * 60) - print("\nšŸ“ Please paste your pyvnt Python code below.") - - print("Type 'END' on a new line when finished, or press Ctrl+D (Linux/Mac) / Ctrl+Z+Enter (Windows)") - print("-" * 40) - - lines = [] - while True: - try: - line = input() - if line.strip().upper() == "END": - break - lines.append(line) - except KeyboardInterrupt: - print("\n\nāš ļø Operation cancelled.") - sys.exit(0) - except EOFError: - break - - return '\n'.join(lines) - -def create_case_to_pyvnt_prompt(context, case_file_content): - """Create prompt for case file to pyvnt conversion""" - return f""" -You are an expert in OpenFOAM case file structure and the pyvnt Python library. - -Here is the context about pyvnt library structure and usage: -{context} - -Now, please convert the following OpenFOAM case file content into equivalent pyvnt Python code: - -``` -{case_file_content} -``` - -Requirements: -1. Generate clean, working Python code using the pyvnt library -2. Follow the exact patterns shown in the context examples -3. Use appropriate property types (Int_P, Flt_P, Str_P, Enm_P) -4. Set reasonable constraints (min/max values) and defaults based on typical OpenFOAM values -5. Always pass `None` as the third argument to Node_C constructor for non-leaf nodes -6. Include the final `show_tree()` call to display the structure -7. Add appropriate comments explaining the structure - -Return ONLY the Python code, no explanations or markdown formatting. -""" - -def create_pyvnt_to_case_prompt(context, pyvnt_code): - """Create prompt for pyvnt to case file conversion""" - return f""" -You are an expert in converting pyvnt Python tree structures to OpenFOAM case file format. - -Here is the detailed context about the conversion process: - -{context} - -Now, please convert the following pyvnt Python code into equivalent OpenFOAM case file format: - -```python -{pyvnt_code} -``` - -IMPORTANT INSTRUCTIONS: -1. **Analyze the complete pyvnt tree structure** - look at all Node_C objects and their relationships -2. **Extract ALL property values** from each Key_C object - each Key_C contains ValueProperty instances -3. **Follow the exact tree hierarchy** - maintain parent-child relationships as nested dictionaries -4. **Use proper OpenFOAM formatting**: - - Dictionary names followed by opening brace - - Key-value pairs with proper spacing and semicolons - - 4-space indentation per level - - Closing braces at appropriate indentation levels -5. **Handle all property types** correctly (Int_P, Flt_P, Str_P, Enm_P) -6. **Format values appropriately** for OpenFOAM (scientific notation, proper strings, etc.) - -Return ONLY the OpenFOAM case file content with proper formatting. Do not include any explanations, code blocks, or markdown formatting. -""" - -def case_to_pyvnt_conversion(model, context, case_file_content): - """Convert OpenFOAM case file to pyvnt code using Gemini""" - prompt = create_case_to_pyvnt_prompt(context, case_file_content) - - print("\n" + "=" * 60) - print("AI-POWERED CONVERSION IN PROGRESS") - print("=" * 60) - - # Start loading indicator - loader = LoadingIndicator("šŸ”„ Generating pyvnt code") - loader.start() - - try: - response = model.generate_content(prompt) - loader.stop() - print("āœ… pyvnt code generated successfully!") - return response.text - except Exception as e: - loader.stop() - print(f"āŒ Error generating code: {e}") - return None - -def pyvnt_to_case_conversion(model, context, pyvnt_code): - """Convert pyvnt Python code to OpenFOAM case file format using Gemini""" - prompt = create_pyvnt_to_case_prompt(context, pyvnt_code) - - print("\n" + "=" * 60) - print("AI-POWERED CONVERSION IN PROGRESS") - print("=" * 60) - - # Start loading indicator - loader = LoadingIndicator("šŸ”„ Generating OpenFOAM case file") - loader.start() - - try: - response = model.generate_content(prompt) - loader.stop() - print("āœ… OpenFOAM case file generated successfully!") - return response.text - except Exception as e: - loader.stop() - print(f"āŒ Error generating case file: {e}") - return None - -def save_output(content, filename, mode, directories): - """Save the generated content to appropriate directory""" - try: - if mode == ConversionMode.CASE_TO_PYVNT: - # Save Python files to pyvnt_package directory - filepath = directories['python'] / filename - print(f"\nšŸ“¦ Saving Python file to pyvnt_package directory...") - else: - # Save text files to generated_text_files directory - filepath = directories['text'] / filename - print(f"\nšŸ“„ Saving text file to generated_text_files directory...") - - with open(filepath, 'w', encoding='utf-8') as f: - f.write(content) - - print(f"āœ… Generated content saved to: {filepath}") - return str(filepath) - except Exception as e: - print(f"āŒ Error saving file: {e}") - return None - -def main(): - """Main function""" - # Create output directories - print("šŸ“ Setting up output directories...") - directories = create_output_directories() - - # Setup Gemini (needed for both conversion modes now) - print("\nšŸ¤– Initializing AI converter...") - model = setup_gemini() - print("āœ… AI converter ready!") - - # Get conversion mode - mode = get_conversion_mode() - - # Load appropriate context - print(f"\nšŸ“š Loading conversion context...") - context = load_context(mode) - - # Get input content - input_content = get_input_content(mode) - - if not input_content.strip(): - print("āŒ No content provided. Exiting.") - sys.exit(1) - - # Perform conversion based on mode (both now use Gemini!) - if mode == ConversionMode.CASE_TO_PYVNT: - result = case_to_pyvnt_conversion(model, context, input_content) - else: - result = pyvnt_to_case_conversion(model, context, input_content) - - if result: - # Clean up the result (remove any markdown formatting if present) - result = result.strip() - if result.startswith('```'): - lines = result.split('\n') - # Remove first line if it's markdown formatting - if lines[0].startswith('```'): - lines = lines[1:] - # Remove last line if it's markdown formatting - if lines and lines[-1].startswith('```'): - lines = lines[:-1] - result = '\n'.join(lines) - - print("\n" + "=" * 60) - print("šŸŽ‰ CONVERSION RESULT:") - print("=" * 60) - print(result) - - # Ask if user wants to save - save_choice = input("\nšŸ’¾ Save this result to a file? (y/n): ").strip().lower() - if save_choice in ['y', 'yes', '']: - # Get custom filename or use default - suggested_filename = get_output_filename(mode) - filename = input(f"šŸ“ Enter filename (default: {suggested_filename}): ").strip() - if not filename: - filename = suggested_filename - else: - filename = get_output_filename(mode, filename) - - # Save to appropriate directory - saved_path = save_output(result, filename, mode, directories) - if saved_path: - print(f"šŸ“‚ File location: {saved_path}") - if mode == ConversionMode.CASE_TO_PYVNT: - print(f"\nāœ… '{filename}' has been generated in the pyvnt_package directory.") - print(f"šŸ‘‰ Head over to the pyvnt_package directory to run the generated file:\n") - print(f" cd pyvnt_package && python3 {filename}\n") - else: - print("āŒ Conversion failed.") - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - print("\n\nāš ļø Operation cancelled by user.") - except Exception as e: - print(f"\nšŸ’„ Unexpected error: {e}") - traceback.print_exc() - sys.exit(1) \ No newline at end of file diff --git a/converter/pyvnt_package/context_case_to_trees.md b/converter/pyvnt_package/context_case_to_trees.md new file mode 100644 index 0000000..2bc5d13 --- /dev/null +++ b/converter/pyvnt_package/context_case_to_trees.md @@ -0,0 +1,192 @@ +# pyvnt: Class Structure and Example Usage Context + +## Core Class Structure + +### 1. ValueProperty (Abstract) +- Parent for all property classes (e.g., `Int_P`, `Flt_P`, `Enm_P`). +- Enforces interface for value properties used in Key_C. + +### 2. Int_P, Flt_P, Str_P, Enm_P +- Store typed values with constraints (e.g., min/max/default for ints/floats, set of choices for enums). +- Example: `Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')` + +### 3. Key_C +- Represents a dictionary-like node holding multiple ValueProperty instances. +- Enforces encapsulation: attributes are only set via constructor or methods. +- Example: `Key_C('solver', prop1, prop2)` +- Methods: `replaceVal`, `delVal`, `giveVal`, etc. + +### 4. Node_C +- Tree node class (inherits from anytree's NodeMixin). +- Holds a name, parent, children, and a list of Key_C objects as data. +- Methods: `addChild`, `setParent`, `add_data`, `removeData`, `getChild`, etc. + +### 5. show_tree +- Utility to print the tree structure and Key_C at each node. + +## Example: Creating and Displaying a Tree + +```python +from pyvnt import * + +# Define properties +prop1 = Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG') +prop2 = Enm_P('val2', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PBiCG') + +# Create Key_C +key1 = Key_C('solver', prop1, prop2) + +# Build tree +head = Node_C("test_head", None, None) +child1 = Node_C('test_child', head, None) +child2 = Node_C('test_child2', child1, None, key1) +child3 = Node_C('test_child3', child1, None, key1) + +# Display tree +show_tree(head) +``` + +## Example Output (Tree Structure) +``` +test_head +|--test_child +| |--test_child2 +| | { +| | solver : PCG, PBiCG, +| | } +| |--test_child3 +| | { +| | solver : PCG, PBiCG, +| | } +``` + +## Complete Example: + +```python +from pyvnt import * + +head = Node_C('fvSolutions') +sl = Node_C('solvers', parent = head) + +s = Key_C('solver', Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')) +pc = Key_C('preconditioner', Enm_P('val1', items={'DIC', 'DILU', 'FDIC'}, default='DIC')) +tol = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1000, default=1e-06)) +rt = Key_C('relTol', Flt_P('val1', minimum=0, maximum=100, default=0.05)) + +p = Node_C('p', sl, None, pc, s, tol, rt) +relTol2 = Key_C('relTol', Flt_P('val1', minimum=0, maximum=100, default=0)) +pf = Node_C('pFinal', sl, None, relTol2) + +sol2 = Key_C('solver', Enm_P('val1', items={'smoothSolver'}, default='smoothSolver')) +sm = Key_C('smoother', Enm_P('val1', items={'symGaussSeidel', 'gaussSeidel'}, default = 'symGaussSeidel')) +tol2 = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1000, default=1e-05)) +relTol3 = Key_C('relTol', Flt_P('val1', minimum=0, maximum=100, default=0)) + +u = Node_C('U', sl, None, sol2, sm, tol2, relTol3) + +ncorr = Key_C('nCorrectors', Int_P('int_prop_1', minimum=0, maximum=100, default=2)) +nnoc = Key_C('nNonOrthogonalCorrectors', Int_P('int_prop_2', minimum=0, maximum=100, default=0)) +prc = Key_C('pRefCell', Int_P('int_prop_3', minimum=0, maximum=100, default=0)) +prv = Key_C('pRefValue', Int_P('int_prop_4', minimum=0, maximum=100, default=0)) + +piso = Node_C('PISO', head, None, ncorr, nnoc, prc, prv) + +show_tree(head) +``` + +### Output +``` +fvSolutions +ā”œā”€ā”€ solvers +│ ā”œā”€ā”€ p +│ │ { +│ │ preconditioner : DIC, +│ │ solver : PCG, +│ │ tolerance : 1e-06, +│ │ relTol : 0.05, +│ │ } +│ ā”œā”€ā”€ pFinal +│ │ { +│ │ relTol : 0, +│ │ } +│ └── U +│ { +│ solver : smoothSolver, +│ smoother : symGaussSeidel, +│ tolerance : 1e-05, +│ relTol : 0, +│ } +└── PISO + { + nCorrectors : 2, + nNonOrthogonalCorrectors : 0, + pRefCell : 0, + pRefValue : 0, + } +``` + +## Important Note on Node_C Construction and Leaf Node Errors + +**Common mistake:** +The way you call the `Node_C` constructor determines whether a node is treated as a leaf (cannot have children) or not. + +### Example from `testfile.py` (Correct) +```python +p = Node_C('p', sl, None, pc, s, tol, rt) +``` +- `'p'` is the node name. +- `sl` is the parent node. +- `None` is explicitly passed as the third argument (children). +- The rest (`pc, s, tol, rt`) are data objects. + +**Why this works:** +Passing `None` for the children argument tells the `Node_C` class that this node is **not a leaf** and can have children added later. The remaining arguments are treated as data. + +--- + +### Example from `main.py` (Potential Issue) +```python +solvers_node = Node_C("solvers", parent=fv_solution_root) +``` +- Here, only the `parent` is specified using a keyword argument. +- If the constructor does not receive the children argument (or receives it as something other than `None` or a list), it may **default to making the node a leaf**. + +--- + +### Why the difference? +- The `Node_C` constructor likely checks if the third argument (children) is `None` or a list. + - If `None`, the node can have children added later. + - If omitted or passed incorrectly, it might default to a leaf node. +- This is why in `testfile.py`, the node is not a leaf, but in `main.py`, it may be. + +--- + +### Recommendation + +**Always pass `None` as the third argument to the `Node_C` constructor if you want the node to be able to have children later.** +This matches the intended usage and avoids the `LeafNodeError`. + +**Example fix for main.py:** +```python +solvers_node = Node_C("solvers", fv_solution_root, None) +``` + +**IMPORTANT** +add_data() only takes 3 positional arguments, never give more than 3. + +## Class Name Mapping (Old vs New Syntax) + +| Old Syntax | New Syntax | +|------------|------------| +| `Foam` | `Node_C` | +| `KeyData` | `Key_C` | +| `PropertyInt` | `Int_P` | +| `PropertyFloat` | `Flt_P` | +| `PropertyString` | `Str_P` | +| `EnumProp` | `Enm_P` | +| `showTree` | `show_tree` | + +**Summary:** +- In `testfile.py`, passing `None` for children tells `Node_C` the node is not a leaf. +- In `main.py`, omitting the children argument may cause the node to be a leaf. +- **Check the `Node_C` class constructor signature and always follow the correct argument order and usage.** \ No newline at end of file diff --git a/converter/pyvnt_package/context_trees_to_case.md b/converter/pyvnt_package/context_trees_to_case.md new file mode 100644 index 0000000..a05f892 --- /dev/null +++ b/converter/pyvnt_package/context_trees_to_case.md @@ -0,0 +1,237 @@ +# pyvnt to OpenFOAM Case File Converter Context + +## Overview +This context describes how to convert pyvnt tree structures back to OpenFOAM case file format. The conversion process involves traversing the Node_C tree structure and generating properly formatted OpenFOAM dictionary syntax. + +## OpenFOAM Dictionary Format Rules + +### 1. Basic Structure +``` +dictionaryName +{ + key1 value1; + key2 value2; + + subDict1 + { + nestedKey1 nestedValue1; + nestedKey2 nestedValue2; + } + + subDict2 + { + // more nested content + } +} +``` + +### 2. Formatting Rules +- Dictionary names are followed by opening brace `{` on the same line or next line +- Key-value pairs are separated by whitespace and terminated with semicolon `;` +- Nested dictionaries follow the same pattern +- Proper indentation (typically 4 spaces per level) +- Comments can be added with `//` or `/* */` + +### 3. Value Types in OpenFOAM +- **Strings**: Can be quoted or unquoted (e.g., `PCG` or `"PCG"`) +- **Numbers**: Integer or floating point (e.g., `1`, `1.0`, `1e-06`) +- **Booleans**: `true`, `false`, `yes`, `no`, `on`, `off` +- **Lists**: `(value1 value2 value3)` or `[value1 value2 value3]` +- **Vectors**: `(x y z)` + +## pyvnt to OpenFOAM Conversion Logic + +### 1. Tree Traversal +- Start from root Node_C +- Recursively traverse all children +- Convert each Node_C to OpenFOAM dictionary format + +### 2. Node_C Conversion +Each Node_C becomes an OpenFOAM dictionary: +```python +# pyvnt Node_C with name "solvers" +solvers_node = Node_C("solvers", parent, None, key1, key2) + +# Converts to OpenFOAM: +solvers +{ + key1_name key1_value; + key2_name key2_value; +} +``` + +### 3. Key_C Conversion +Each Key_C becomes a key-value pair: +```python +# pyvnt Key_C +tolerance = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1000, default=1e-06)) + +# Converts to OpenFOAM: +tolerance 1e-06; +``` + +### 4. Property Value Extraction +- **Int_P**: Extract integer value +- **Flt_P**: Extract float value (format appropriately, e.g., scientific notation) +- **Str_P**: Extract string value +- **Enm_P**: Extract selected enum value + +## Example Conversion + +### Input pyvnt Tree: +```python +from pyvnt import * + +head = Node_C('fvSolutions') +sl = Node_C('solvers', parent=head) + +s = Key_C('solver', Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')) +tol = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1000, default=1e-06)) +p = Node_C('p', sl, None, s, tol) + +ncorr = Key_C('nCorrectors', Int_P('int_prop_1', minimum=0, maximum=100, default=2)) +piso = Node_C('PISO', head, None, ncorr) +``` + +### Output OpenFOAM Format: +``` +fvSolutions +{ + solvers + { + p + { + solver PCG; + tolerance 1e-06; + } + } + + PISO + { + nCorrectors 2; + } +} +``` + +## Implementation Guidelines + +### 1. Recursive Tree Traversal +```python +def convert_node_to_openfoam(node, indent_level=0): + indent = " " * indent_level + lines = [] + + # Add node name and opening brace + lines.append(f"{indent}{node.name}") + lines.append(f"{indent}{{") + + # Convert Key_C objects to key-value pairs + for key_data in node.data: + key_line = convert_key_to_openfoam(key_data, indent_level + 1) + lines.append(key_line) + + # Recursively convert children + for child in node.children: + child_lines = convert_node_to_openfoam(child, indent_level + 1) + lines.extend(child_lines) + + # Add closing brace + lines.append(f"{indent}}}") + + return lines +``` + +### 2. Key_C to OpenFOAM Conversion +```python +def convert_key_to_openfoam(key_data, indent_level=0): + indent = " " * indent_level + + # Extract value from the ValueProperty + value = extract_property_value(key_data) + + # Format based on value type + formatted_value = format_openfoam_value(value) + + return f"{indent}{key_data.name} {formatted_value};" +``` + +### 3. Value Formatting +```python +def format_openfoam_value(value): + if isinstance(value, str): + return value + elif isinstance(value, int): + return str(value) + elif isinstance(value, float): + if value == 0: + return "0" + elif abs(value) < 1e-4 or abs(value) > 1e4: + return f"{value:.6e}" + else: + return str(value) + else: + return str(value) +``` + +## Error Handling and Edge Cases + +### 1. Empty Nodes +- Nodes with no data and no children should still generate dictionary structure +- Add comment indicating empty dictionary if needed + +### 2. Special Characters +- Handle special characters in names and values +- Quote strings if they contain spaces or special characters + +### 3. File Headers +- Add appropriate OpenFOAM file headers (FoamFile dictionary) +- Include version, format, class, object, etc. + +### 4. Value Validation +- Ensure extracted values are valid OpenFOAM syntax +- Handle edge cases like infinity, NaN, etc. + +## Complete Example Function Structure + +```python +def pyvnt_to_openfoam(root_node, add_foam_header=True): + """ + Convert pyvnt tree to OpenFOAM case file format + + Args: + root_node: Root Node_C of the pyvnt tree + add_foam_header: Whether to add FoamFile header + + Returns: + str: OpenFOAM formatted case file content + """ + lines = [] + + if add_foam_header: + lines.extend(generate_foam_header(root_node.name)) + + # Convert the tree + tree_lines = convert_node_to_openfoam(root_node) + lines.extend(tree_lines) + + return '\n'.join(lines) +``` + +## Usage Notes + +### 1. Accessing Node Data +- Use `node.data` to access list of Key_C objects +- Use `node.children` to access child nodes +- Use `node.name` for the dictionary name + +### 2. Extracting Property Values +- Each Key_C contains ValueProperty objects +- Use appropriate methods to extract current values +- Handle default values appropriately + +### 3. Indentation and Formatting +- Maintain consistent 4-space indentation +- Align values for readability +- Add blank lines between major sections + +This context provides the foundation for implementing the pyvnt to OpenFOAM conversion functionality. \ No newline at end of file diff --git a/converter/pyvnt_package/converter.py b/converter/pyvnt_package/converter.py new file mode 100644 index 0000000..99a7b6a --- /dev/null +++ b/converter/pyvnt_package/converter.py @@ -0,0 +1,861 @@ +#!/usr/bin/env python3 +""" +Enhanced Bidirectional Converter: Case Files ↔ pyvnt Python Code +Now includes read() function for programmatic file conversion! +""" +import os +import sys +import ast +import traceback +import re +import threading +import time +import google.generativeai as genai +from pathlib import Path +from datetime import datetime +import importlib.util +import types + +class LoadingIndicator: + """A simple loading indicator with animated dots""" + def __init__(self, message="Loading"): + self.message = message + self.is_running = False + self.thread = None + + def start(self): + """Start the loading indicator""" + self.is_running = True + self.thread = threading.Thread(target=self._animate) + self.thread.daemon = True + self.thread.start() + + def stop(self): + """Stop the loading indicator""" + self.is_running = False + if self.thread: + self.thread.join() + # Clear the line + print("\r" + " " * (len(self.message) + 10), end="\r") + + def _animate(self): + """Animate the loading dots""" + dots = 0 + while self.is_running: + dots_str = "." * (dots % 4) + print(f"\r{self.message}{dots_str:<3}", end="", flush=True) + time.sleep(0.5) + dots += 1 + +class ConversionMode: + CASE_TO_PYVNT = "case_to_pyvnt" + PYVNT_TO_CASE = "pyvnt_to_case" + +class RootNodeExtractor: + """Extract root node variable from generated pyvnt code""" + + @staticmethod + def find_root_variable(code_content): + """ + Find the root node variable by analyzing the AST + Root node is typically: Node_C('name', None, None, ...) + """ + try: + tree = ast.parse(code_content) + root_candidates = [] + + for node in ast.walk(tree): + if isinstance(node, ast.Assign): + # Look for assignments that create Node_C with parent=None + if isinstance(node.value, ast.Call): + if (hasattr(node.value.func, 'id') and + node.value.func.id == 'Node_C' and + len(node.value.args) >= 2): + + # Check if second argument (parent) is None + second_arg = node.value.args[1] + if isinstance(second_arg, ast.Constant) and second_arg.value is None: + if node.targets and isinstance(node.targets[0], ast.Name): + root_candidates.append(node.targets[0].id) + + # Return the first root candidate (there should typically be only one) + return root_candidates[0] if root_candidates else None + + except Exception as e: + print(f"Warning: Could not analyze code structure: {e}") + return None + + @staticmethod + def extract_from_show_tree(code_content): + """ + Fallback method: extract root variable from show_tree() call + """ + try: + # Look for show_tree(variable_name) pattern + pattern = r'show_tree\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)' + matches = re.findall(pattern, code_content) + return matches[0] if matches else None + except Exception: + return None + +def create_output_directories(): + """Create output directories if they don't exist""" + directories = { + 'python': Path("pyvnt_package"), + 'text': Path("generated_text_files"), + 'modules': Path("pyvnt_package") # New directory for generated modules + } + + for dir_type, directory in directories.items(): + try: + directory.mkdir(exist_ok=True) + if dir_type != 'modules': # Don't print for internal modules directory + print(f"āœ… Directory ready: {directory}") + except Exception as e: + print(f"āŒ Error creating directory {directory}: {e}") + sys.exit(1) + + return directories + +def setup_gemini(): + """Setup Gemini API client""" + api_key = os.getenv('GEMINI_API_KEY') + if not api_key: + print("Error: GEMINI_API_KEY environment variable not set") + print("Please set your Gemini API key: export GEMINI_API_KEY=your_api_key_here") + sys.exit(1) + + genai.configure(api_key=api_key) + model = genai.GenerativeModel('gemini-2.0-flash') + return model + +def load_context(mode): + """Load the appropriate context based on conversion mode""" + if mode == ConversionMode.CASE_TO_PYVNT: + context_file = Path("context_case_to_trees.md") + fallback_context = get_case_to_pyvnt_context() + else: + context_file = Path("context_trees_to_case.md") + fallback_context = get_pyvnt_to_openfoam_context() + + if not context_file.exists(): + return fallback_context + + try: + with open(context_file, 'r', encoding='utf-8') as f: + return f.read() + except Exception: + return fallback_context + +def get_case_to_pyvnt_context(): + """Embedded context for case file to pyvnt conversion - ENFORCES root variable name""" + return """ +# pyvnt: Class Structure and Example Usage Context + +## Core Class Structure + +### 1. ValueProperty (Abstract) +- Parent for all property classes (e.g., `Int_P`, `Flt_P`, `Enm_P`). +- Enforces interface for value properties used in Key_C. + +### 2. Int_P, Flt_P, Str_P, Enm_P +- Store typed values with constraints (e.g., min/max/default for ints/floats, set of choices for enums). +- Example: `Enm_P('val1', items={'PCG', 'PBiCG', 'PBiCGStab'}, default='PCG')` + +### 3. Key_C +- Represents a dictionary-like node holding multiple ValueProperty instances. +- Enforces encapsulation: attributes are only set via constructor or methods. +- Example: `Key_C('solver', prop1, prop2)` + +### 4. Node_C +- Tree node class (inherits from anytree's NodeMixin). +- Holds a name, parent, children, and a list of Key_C objects as data. +- Always pass `None` as the third argument for non-leaf nodes. +- Example: `Node_C('nodeName', parent, None, data1, data2)` + +### 5. show_tree +- Utility to print the tree structure and Key_C at each node. + +## CRITICAL REQUIREMENTS: +- Always start with: `from pyvnt import *` +- MUST create root node with variable name 'root': `root = Node_C('name', None, None)` +- End with: `show_tree(root)` +- The root variable MUST be named 'root' - no other name is acceptable +""" + +def get_pyvnt_to_openfoam_context(): + """Enhanced embedded context for pyvnt to OpenFOAM conversion""" + return """ +# pyvnt to OpenFOAM Case File Converter Context + +You are an expert in converting pyvnt Python tree structures to OpenFOAM case file format. + +## Key Conversion Rules: + +1. **Node_C Structure**: Each Node_C represents a dictionary/subdictionary in OpenFOAM format +2. **Key_C to Properties**: Each Key_C contains ValueProperty instances that become key-value pairs +3. **Property Value Extraction**: Extract actual values from .value or .default attributes +4. **OpenFOAM Formatting**: Use proper dictionary syntax with braces, semicolons, and 4-space indentation + +## Conversion Process: +1. Traverse the Node_C tree recursively +2. For each Node_C: create OpenFOAM dictionary with proper braces +3. For each Key_C in node.data: extract property values and format as key-value pairs +4. Maintain hierarchical structure through proper nesting + +## Example: +```python +# pyvnt structure +p_node = Node_C('p', parent, None, + Key_C('solver', Enm_P('val1', default='PCG')), + Key_C('tolerance', Flt_P('val1', default=1e-06)) +) + +# OpenFOAM output +p +{ + solver PCG; + tolerance 1e-06; +} +``` + +Extract ALL property values from every Key_C object in the tree structure. +""" + +def create_case_to_pyvnt_prompt(context, case_file_content): + """Create prompt for case file to pyvnt conversion - ENFORCES root variable name""" + return f""" +You are an expert in OpenFOAM case file structure and the pyvnt Python library. + +Here is the context about pyvnt library structure and usage: +{context} + +Now, please convert the following OpenFOAM case file content into equivalent pyvnt Python code: + +``` +{case_file_content} +``` + +Requirements: +1. Generate clean, working Python code using the pyvnt library +2. Follow the exact patterns shown in the context examples +3. Use appropriate property types (Int_P, Flt_P, Str_P, Enm_P) +4. Set reasonable constraints (min/max values) and defaults based on typical OpenFOAM values +5. Always pass `None` as the third argument to Node_C constructor for non-leaf nodes +6. Include the final `show_tree(root)` call to display the structure +7. Add appropriate comments explaining the structure +8. MUST start with `from pyvnt import *` +9. CRITICAL: The root node variable MUST be named 'root' - example: `root = Node_C('fvSolutions', None, None)` + +Return ONLY the Python code, no explanations or markdown formatting. +""" + +def validate_generated_code(code_content): + """ + Validate the generated pyvnt code before execution + """ + try: + # Try to parse the code + ast.parse(code_content) + + # Check for required imports + if 'from pyvnt import *' not in code_content and 'import pyvnt' not in code_content: + return False, "Missing pyvnt import statement" + + # Check for Node_C usage + if 'Node_C(' not in code_content: + return False, "No Node_C objects found" + + # Check for show_tree call + if 'show_tree(' not in code_content: + return False, "Missing show_tree call" + + return True, "Code validation successful" + + except SyntaxError as e: + return False, f"Syntax error: {e}" + except Exception as e: + return False, f"Validation error: {e}" + +def execute_pyvnt_code(code_content): + """ + Execute pyvnt code in an isolated namespace and return the root node + """ + try: + # Create isolated namespace + namespace = {} + + # Add necessary imports to namespace + exec("from pyvnt import *", namespace) + + # Execute the generated code + exec(code_content, namespace) + + # Extract root node variable + root_var_name = RootNodeExtractor.find_root_variable(code_content) + + if not root_var_name: + # Fallback: try to extract from show_tree call + root_var_name = RootNodeExtractor.extract_from_show_tree(code_content) + + if not root_var_name: + # Last resort: look for Node_C objects with parent=None + for var_name, obj in namespace.items(): + if (hasattr(obj, 'parent') and obj.parent is None and + hasattr(obj, 'name') and not var_name.startswith('_')): + root_var_name = var_name + break + + if root_var_name and root_var_name in namespace: + return namespace[root_var_name], root_var_name + else: + raise ValueError("Could not identify root node variable") + + except Exception as e: + raise RuntimeError(f"Failed to execute pyvnt code: {e}") + +def save_as_module(code_content, module_name, directories): + """ + Save the generated code as a reusable module + """ + try: + module_path = directories['modules'] / f"{module_name}.py" + + with open(module_path, 'w', encoding='utf-8') as f: + f.write(code_content) + + return str(module_path) + except Exception as e: + print(f"Warning: Could not save module: {e}") + return None + +def case_to_pyvnt_conversion(model, context, case_file_content): + """Convert OpenFOAM case file to pyvnt code using Gemini""" + prompt = create_case_to_pyvnt_prompt(context, case_file_content) + + try: + response = model.generate_content(prompt) + result = response.text.strip() + + # Clean up markdown formatting if present + if result.startswith('```'): + lines = result.split('\n') + if lines[0].startswith('```'): + lines = lines[1:] + if lines and lines[-1].startswith('```'): + lines = lines[:-1] + result = '\n'.join(lines) + + return result + except Exception as e: + raise RuntimeError(f"AI conversion failed: {e}") + +def execute_generated_code_directly(generated_code, pyvnt_package_path=None): + """ + Execute the generated code directly and return the root variable + """ + import sys + from pathlib import Path + + # Add the pyvnt_package directory to Python path so it can find pyvnt + if pyvnt_package_path is None: + pyvnt_package_path = str(Path("pyvnt_package").absolute()) + + path_added = False + + try: + if pyvnt_package_path not in sys.path: + sys.path.insert(0, pyvnt_package_path) + path_added = True + + # Create a local namespace for execution + local_namespace = {} + global_namespace = globals().copy() + + # Execute the generated code + exec(generated_code, global_namespace, local_namespace) + + # Get the root variable + if 'root' in local_namespace: + return local_namespace['root'] + else: + raise AttributeError("Generated code does not create a 'root' variable") + + except Exception as e: + raise RuntimeError(f"Failed to execute generated code: {e}") + finally: + # Clean up sys.path + if path_added and pyvnt_package_path in sys.path: + sys.path.remove(pyvnt_package_path) + + +def create_output_directories(): + """ + Create necessary output directories including tree_modules + """ + from pathlib import Path + + directories = { + 'base': Path("converter"), + 'generated_files': Path("converter/generated_text_files"), + 'pyvnt_package': Path("converter/pyvnt_package"), + 'tree_modules': Path("converter/pyvnt_package/tree_modules"), # Inside pyvnt_package + 'modules': Path("converter/pyvnt_package") # Keep for backward compatibility + } + + # Create all directories + for dir_path in directories.values(): + dir_path.mkdir(parents=True, exist_ok=True) + + return directories + + +def read(file_path, save_module=True, module_name=None, verbose=False, use_tree_modules=True): + """ + Read an OpenFOAM case file and convert it to a pyvnt tree structure. + + Args: + file_path (str): Path to the OpenFOAM case file + save_module (bool): Whether to save the generated code as a reusable module + module_name (str): Custom name for the saved module (optional) + verbose (bool): Whether to print detailed progress information + use_tree_modules (bool): Whether to save modules in tree_modules/ directory + + Returns: + Node_C: The root node of the constructed pyvnt tree + + Raises: + FileNotFoundError: If the input file doesn't exist + RuntimeError: If conversion or execution fails + """ + + # Validate input file + file_path = Path(file_path) + if not file_path.exists(): + raise FileNotFoundError(f"File not found: {file_path}") + + if verbose: + print(f"šŸ”„ Reading file: {file_path}") + + # Read file content + try: + with open(file_path, 'r', encoding='utf-8') as f: + case_content = f.read() + except Exception as e: + raise RuntimeError(f"Failed to read file: {e}") + + if not case_content.strip(): + raise ValueError("File is empty") + + # Setup components + if verbose: + print("šŸ¤– Initializing AI converter...") + + model = setup_gemini() + context = load_context(ConversionMode.CASE_TO_PYVNT) + directories = create_output_directories() + + # Convert using AI + if verbose: + print("šŸ”„ Converting to pyvnt code...") + loader = LoadingIndicator("Converting") + loader.start() + + try: + generated_code = case_to_pyvnt_conversion(model, context, case_content) + if verbose: + loader.stop() + except Exception as e: + if verbose: + loader.stop() + raise e + + # Validate generated code + is_valid, validation_msg = validate_generated_code(generated_code) + if not is_valid: + raise RuntimeError(f"Generated code validation failed: {validation_msg}") + + if verbose: + print("āœ… Code validation successful") + + # Execute the generated code directly + if verbose: + print("šŸ”„ Executing generated code directly...") + + try: + # Pass the pyvnt_package path to ensure the module can be found + pyvnt_package_path = str(directories['pyvnt_package'].absolute()) + root_tree = execute_generated_code_directly(generated_code, pyvnt_package_path) + except Exception as e: + raise RuntimeError(f"Failed to execute generated code: {e}") + + # Optionally save the module file + if save_module: + # Generate module name if not provided + if not module_name: + module_name = f"converted_{file_path.stem}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + + # Choose directory based on use_tree_modules flag + if use_tree_modules: + module_dir = directories['tree_modules'] + if verbose: + print(f"šŸ“ Saving to tree_modules/ directory...") + else: + module_dir = directories['modules'] + if verbose: + print(f"šŸ“ Saving to pyvnt_package/ directory...") + + module_path = module_dir / f"{module_name}.py" + + try: + with open(module_path, 'w', encoding='utf-8') as f: + f.write(generated_code) + + if verbose: + print(f"šŸ’¾ Module saved: {module_path}") + except Exception as e: + if verbose: + print(f"āš ļø Failed to save module: {e}") + + if verbose: + print(f"āœ… Conversion completed! Root tree created successfully") + print("🌳 Tree structure:") + # Try to display the tree structure + try: + from pyvnt import show_tree + show_tree(root_tree) + except (ImportError, NameError): + if verbose: + print("show_tree function not available") + + return root_tree + + +def load_saved_module(module_name, from_tree_modules=True, verbose=False): + """ + Load a previously saved module and return its root tree. + + Args: + module_name (str): Name of the module to load (without .py extension) + from_tree_modules (bool): Whether to load from tree_modules/ or pyvnt_package/ + verbose (bool): Whether to print progress information + + Returns: + Node_C: The root node of the loaded pyvnt tree + """ + from pathlib import Path + import sys + + # Determine the correct directory + if from_tree_modules: + module_dir = Path("converter/pyvnt_package/tree_modules") + else: + module_dir = Path("converter/pyvnt_package") + + module_path = module_dir / f"{module_name}.py" + + if not module_path.exists(): + raise FileNotFoundError(f"Module not found: {module_path}") + + if verbose: + print(f"šŸ“– Loading module: {module_path}") + + # Read the module content + try: + with open(module_path, 'r', encoding='utf-8') as f: + module_code = f.read() + except Exception as e: + raise RuntimeError(f"Failed to read module: {e}") + + # Execute the module code + try: + pyvnt_package_path = str(Path("converter/pyvnt_package").absolute()) + root_tree = execute_generated_code_directly(module_code, pyvnt_package_path) + + if verbose: + print("āœ… Module loaded successfully!") + + return root_tree + + except Exception as e: + raise RuntimeError(f"Failed to execute module: {e}") + + +def list_saved_modules(from_tree_modules=True): + """ + List all saved modules in the specified directory. + + Args: + from_tree_modules (bool): Whether to list from tree_modules/ or pyvnt_package/ + + Returns: + list: List of module names (without .py extension) + """ + from pathlib import Path + + if from_tree_modules: + module_dir = Path("converter/pyvnt_package/tree_modules") + else: + module_dir = Path("converter/pyvnt_package") + + if not module_dir.exists(): + return [] + + # Get all .py files that start with "converted_" + modules = [] + for py_file in module_dir.glob("converted_*.py"): + modules.append(py_file.stem) + + return sorted(modules) + + +# Legacy interactive functions (keeping for backward compatibility) + +def get_conversion_mode(): + """Get conversion mode from user""" + print("=" * 70) + print("šŸš€ BIDIRECTIONAL CONVERTER: OpenFOAM ↔ pyvnt (AI-Powered)") + print("=" * 70) + print("\nSelect conversion mode:") + print("1. šŸ“„ OpenFOAM Case Files → šŸ pyvnt Python Code") + print("2. šŸ pyvnt Python Code → šŸ“„ OpenFOAM Case Files") + print("3. šŸ”§ Use read() function (programmatic)") + print("4. 🚪 Exit") + + while True: + try: + choice = input("\nEnter your choice (1-4): ").strip() + if choice == "1": + return ConversionMode.CASE_TO_PYVNT + elif choice == "2": + return ConversionMode.PYVNT_TO_CASE + elif choice == "3": + return "PROGRAMMATIC" + elif choice == "4": + print("šŸ‘‹ Goodbye!") + sys.exit(0) + else: + print("āŒ Invalid choice. Please enter 1-4.") + except KeyboardInterrupt: + print("\n\nāš ļø Operation cancelled.") + sys.exit(0) + except EOFError: + print("\n\nāš ļø Operation cancelled.") + sys.exit(0) + +def demonstrate_read_function(): + """Demonstrate the read() function usage""" + print("\n" + "=" * 60) + print("šŸ”§ PROGRAMMATIC CONVERSION DEMO") + print("=" * 60) + + file_path = input("Enter the path to your OpenFOAM case file: ").strip() + + try: + print("\nšŸš€ Using read() function...") + root_node, root_var_name = read(file_path, verbose=True) + + print(f"\nāœ… Success! You can now use '{root_var_name}' as your pyvnt tree.") + print(f"Root node type: {type(root_node)}") + print(f"Root node name: {root_node.name}") + + # Show how to use it + print(f"\nšŸ’” Usage example:") + print(f" root_node, var_name = read('{file_path}')") + print(f" # Now you can use root_node in your code!") + + except Exception as e: + print(f"āŒ Error: {e}") + +# Keep other existing functions for backward compatibility +def get_input_content(mode): + """Get input content based on conversion mode""" + if mode == ConversionMode.CASE_TO_PYVNT: + print("\n" + "=" * 60) + print("šŸ“„ CASE FILE TO PYVNT CONVERTER") + print("=" * 60) + print("\nšŸ“ Please paste your OpenFOAM case file content below.") + else: + print("\n" + "=" * 60) + print("šŸ PYVNT TO CASE FILE CONVERTER") + print("=" * 60) + print("\nšŸ“ Please paste your pyvnt Python code below.") + + print("Type 'END' on a new line when finished, or press Ctrl+D (Linux/Mac) / Ctrl+Z+Enter (Windows)") + print("-" * 40) + + lines = [] + while True: + try: + line = input() + if line.strip().upper() == "END": + break + lines.append(line) + except KeyboardInterrupt: + print("\n\nāš ļø Operation cancelled.") + sys.exit(0) + except EOFError: + break + + return '\n'.join(lines) + +def get_output_filename(mode, custom_name=None): + """Generate appropriate filename with timestamp""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + + if custom_name: + if mode == ConversionMode.CASE_TO_PYVNT: + if not custom_name.endswith('.py'): + custom_name += '.py' + else: + if not custom_name.endswith('.txt'): + custom_name += '.txt' + return custom_name + + if mode == ConversionMode.CASE_TO_PYVNT: + return f"pyvnt_code_{timestamp}.py" + else: + return f"openfoam_case_{timestamp}.txt" + +def create_pyvnt_to_case_prompt(context, pyvnt_code): + """Create prompt for pyvnt to case file conversion""" + return f""" +You are an expert in converting pyvnt Python tree structures to OpenFOAM case file format. + +Here is the detailed context about the conversion process: + +{context} + +Now, please convert the following pyvnt Python code into equivalent OpenFOAM case file format: + +```python +{pyvnt_code} +``` + +IMPORTANT INSTRUCTIONS: +1. **Analyze the complete pyvnt tree structure** - look at all Node_C objects and their relationships +2. **Extract ALL property values** from each Key_C object - each Key_C contains ValueProperty instances +3. **Follow the exact tree hierarchy** - maintain parent-child relationships as nested dictionaries +4. **Use proper OpenFOAM formatting**: + - Dictionary names followed by opening brace + - Key-value pairs with proper spacing and semicolons + - 4-space indentation per level + - Closing braces at appropriate indentation levels +5. **Handle all property types** correctly (Int_P, Flt_P, Str_P, Enm_P) +6. **Format values appropriately** for OpenFOAM (scientific notation, proper strings, etc.) + +Return ONLY the OpenFOAM case file content with proper formatting. Do not include any explanations, code blocks, or markdown formatting. +""" + +def pyvnt_to_case_conversion(model, context, pyvnt_code): + """Convert pyvnt Python code to OpenFOAM case file format using Gemini""" + prompt = create_pyvnt_to_case_prompt(context, pyvnt_code) + + print("\n" + "=" * 60) + print("AI-POWERED CONVERSION IN PROGRESS") + print("=" * 60) + + loader = LoadingIndicator("šŸ”„ Generating OpenFOAM case file") + loader.start() + + try: + response = model.generate_content(prompt) + loader.stop() + print("āœ… OpenFOAM case file generated successfully!") + return response.text + except Exception as e: + loader.stop() + print(f"āŒ Error generating case file: {e}") + return None + +def save_output(content, filename, mode, directories): + """Save the generated content to appropriate directory""" + try: + if mode == ConversionMode.CASE_TO_PYVNT: + filepath = directories['python'] / filename + print(f"\nšŸ“¦ Saving Python file to pyvnt_package directory...") + else: + filepath = directories['text'] / filename + print(f"\nšŸ“„ Saving text file to generated_text_files directory...") + + with open(filepath, 'w', encoding='utf-8') as f: + f.write(content) + + print(f"āœ… Generated content saved to: {filepath}") + return str(filepath) + except Exception as e: + print(f"āŒ Error saving file: {e}") + return None + +def main(): + """Main function with enhanced options""" + print("šŸ“ Setting up output directories...") + directories = create_output_directories() + + mode = get_conversion_mode() + + if mode == "PROGRAMMATIC": + demonstrate_read_function() + return + + print("\nšŸ¤– Initializing AI converter...") + model = setup_gemini() + print("āœ… AI converter ready!") + + print(f"\nšŸ“š Loading conversion context...") + context = load_context(mode) + + input_content = get_input_content(mode) + + if not input_content.strip(): + print("āŒ No content provided. Exiting.") + sys.exit(1) + + if mode == ConversionMode.CASE_TO_PYVNT: + result = case_to_pyvnt_conversion(model, context, input_content) + else: + result = pyvnt_to_case_conversion(model, context, input_content) + + if result: + result = result.strip() + if result.startswith('```'): + lines = result.split('\n') + if lines[0].startswith('```'): + lines = lines[1:] + if lines and lines[-1].startswith('```'): + lines = lines[:-1] + result = '\n'.join(lines) + + print("\n" + "=" * 60) + print("šŸŽ‰ CONVERSION RESULT:") + print("=" * 60) + print(result) + + save_choice = input("\nšŸ’¾ Save this result to a file? (y/n): ").strip().lower() + if save_choice in ['y', 'yes', '']: + suggested_filename = get_output_filename(mode) + filename = input(f"šŸ“ Enter filename (default: {suggested_filename}): ").strip() + if not filename: + filename = suggested_filename + else: + filename = get_output_filename(mode, filename) + + saved_path = save_output(result, filename, mode, directories) + if saved_path: + print(f"šŸ“‚ File location: {saved_path}") + if mode == ConversionMode.CASE_TO_PYVNT: + print(f"\nāœ… '{filename}' has been generated in the pyvnt_package directory.") + print(f"šŸ‘‰ Head over to the pyvnt_package directory to run the generated file:\n") + print(f" cd pyvnt_package && python3 {filename}\n") + else: + print("āŒ Conversion failed.") + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\n\nāš ļø Operation cancelled by user.") + except Exception as e: + print(f"\nšŸ’„ Unexpected error: {e}") + traceback.print_exc() + sys.exit(1) \ No newline at end of file diff --git a/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSchemes_20250720_093827.py b/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSchemes_20250720_093827.py new file mode 100644 index 0000000..d829222 --- /dev/null +++ b/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSchemes_20250720_093827.py @@ -0,0 +1,86 @@ +from pyvnt import * + +# Create the root node +root = Node_C('fvSchemes', None, None) + +# FoamFile node +foamFile_node = Node_C('FoamFile', root, None) + +# FoamFile Key_Cs +format_prop = Str_P('format', default='ascii') +class_prop = Str_P('class', default='dictionary') +location_prop = Str_P('location', default='system') +object_prop = Str_P('object', default='fvSchemes') + +foamFile_key = Key_C('FoamFile_data', format_prop, class_prop, location_prop, object_prop) + +foamFile_node.data.append(foamFile_key) + + +# ddtSchemes node +ddtSchemes_node = Node_C('ddtSchemes', root, None) + +# ddtSchemes Key_Cs +ddt_default_prop = Enm_P('default', items={'Euler', ' CrankNicolson', 'backward'}, default='Euler') + +ddtSchemes_key = Key_C('ddtSchemes_data', ddt_default_prop) +ddtSchemes_node.data.append(ddtSchemes_key) + + +# gradSchemes node +gradSchemes_node = Node_C('gradSchemes', root, None) + +# gradSchemes Key_Cs +grad_default_prop = Str_P('default', default='Gauss linear') + +gradSchemes_key = Key_C('gradSchemes_data', grad_default_prop) +gradSchemes_node.data.append(gradSchemes_key) + +# divSchemes node +divSchemes_node = Node_C('divSchemes', root, None) + +# divSchemes Key_Cs +div_default_prop = Str_P('default', default='none') +div_phiU_prop = Str_P('div(phi,U)', default='Gauss limitedLinearV 1') +div_phiK_prop = Str_P('div(phi,k)', default='Gauss limitedLinear 1') +div_phiEpsilon_prop = Str_P('div(phi,epsilon)', default='Gauss limitedLinear 1') +div_phiOmega_prop = Str_P('div(phi,omega)', default='Gauss limitedLinear 1') +div_phiR_prop = Str_P('div(phi,R)', default='Gauss limitedLinear 1') +div_R_prop = Str_P('div(R)', default='Gauss linear') +div_phiNuTilda_prop = Str_P('div(phi,nuTilda)', default='Gauss limitedLinear 1') +div_nuEff_prop = Str_P('div((nuEff*dev2(T(grad(U)))))', default='Gauss linear') + +divSchemes_key = Key_C('divSchemes_data', div_default_prop, div_phiU_prop, div_phiK_prop, div_phiEpsilon_prop, div_phiOmega_prop, div_phiR_prop, div_R_prop, div_phiNuTilda_prop, div_nuEff_prop) +divSchemes_node.data.append(divSchemes_key) + + +# laplacianSchemes node +laplacianSchemes_node = Node_C('laplacianSchemes', root, None) + +# laplacianSchemes Key_Cs +laplacian_default_prop = Str_P('default', default='Gauss linear corrected') + +laplacianSchemes_key = Key_C('laplacianSchemes_data', laplacian_default_prop) +laplacianSchemes_node.data.append(laplacianSchemes_key) + + +# interpolationSchemes node +interpolationSchemes_node = Node_C('interpolationSchemes', root, None) + +# interpolationSchemes Key_Cs +interpolation_default_prop = Str_P('default', default='linear') + +interpolationSchemes_key = Key_C('interpolationSchemes_data', interpolation_default_prop) +interpolationSchemes_node.data.append(interpolationSchemes_key) + + +# snGradSchemes node +snGradSchemes_node = Node_C('snGradSchemes', root, None) + +# snGradSchemes Key_Cs +snGrad_default_prop = Str_P('default', default='corrected') + +snGradSchemes_key = Key_C('snGradSchemes_data', snGrad_default_prop) +snGradSchemes_node.data.append(snGradSchemes_key) + +show_tree(root) \ No newline at end of file diff --git a/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSchemes_20250720_094230.py b/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSchemes_20250720_094230.py new file mode 100644 index 0000000..4d06749 --- /dev/null +++ b/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSchemes_20250720_094230.py @@ -0,0 +1,65 @@ +from pyvnt import * + +# Create the root node +root = Node_C('fvSchemes', None, None) + +# FoamFile node +foamFile_data = [ + Str_P('format', default='ascii'), + Str_P('class', default='dictionary'), + Str_P('location', default='"system"'), + Str_P('object', default='fvSchemes') +] +foamFile = Node_C('FoamFile', root, None, *[Key_C('FoamFile', *foamFile_data)]) + +# ddtSchemes node +ddtSchemes_data = [ + Str_P('default', default='steadyState') +] +ddtSchemes = Node_C('ddtSchemes', root, None, Key_C('ddtSchemes', *ddtSchemes_data)) + +# gradSchemes node +gradSchemes_data = [ + Str_P('default', default='Gauss linear') +] +gradSchemes = Node_C('gradSchemes', root, None, Key_C('gradSchemes', *gradSchemes_data)) + +# divSchemes node +divSchemes_data = [ + Str_P('default', default='none'), + Str_P('div(phi,U)', default='bounded Gauss linearUpwind grad(U)'), + Str_P('div(phi,k)', default='bounded Gauss limitedLinear 1'), + Str_P('div(phi,epsilon)', default='bounded Gauss limitedLinear 1'), + Str_P('div(phi,omega)', default='bounded Gauss limitedLinear 1'), + Str_P('div(phi,v2)', default='bounded Gauss limitedLinear 1'), + Str_P('div((nuEff*dev2(T(grad(U)))))', default='Gauss linear'), + Str_P('div(nonlinearStress)', default='Gauss linear') +] +divSchemes = Node_C('divSchemes', root, None, Key_C('divSchemes', *divSchemes_data)) + +# laplacianSchemes node +laplacianSchemes_data = [ + Str_P('default', default='Gauss linear corrected') +] +laplacianSchemes = Node_C('laplacianSchemes', root, None, Key_C('laplacianSchemes', *laplacianSchemes_data)) + +# interpolationSchemes node +interpolationSchemes_data = [ + Str_P('default', default='linear') +] +interpolationSchemes = Node_C('interpolationSchemes', root, None, Key_C('interpolationSchemes', *interpolationSchemes_data)) + +# snGradSchemes node +snGradSchemes_data = [ + Str_P('default', default='corrected') +] +snGradSchemes = Node_C('snGradSchemes', root, None, Key_C('snGradSchemes', *snGradSchemes_data)) + +# wallDist node +wallDist_data = [ + Str_P('method', default='meshWave') +] +wallDist = Node_C('wallDist', root, None, Key_C('wallDist', *wallDist_data)) + +# Display the tree +show_tree(root) \ No newline at end of file diff --git a/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSolution_20250720_095416.py b/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSolution_20250720_095416.py new file mode 100644 index 0000000..701fb0f --- /dev/null +++ b/converter/pyvnt_package/converter/pyvnt_package/tree_modules/converted_fvSolution_20250720_095416.py @@ -0,0 +1,50 @@ +from pyvnt import * + +# Create the root node: fvSolution +root = Node_C('fvSolution', None, None) + +# Create the 'solvers' node +solvers_node = Node_C('solvers', root, None) + +# Create Key_C objects for 'p' solver +p_solver = Key_C('solver', Enm_P('val1', items={'GAMG', 'PCG', 'PBiCGStab', 'smoothSolver'}, default='GAMG')) +p_tolerance = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1, default=1e-06)) +p_relTol = Key_C('relTol', Flt_P('val1', minimum=0, maximum=1, default=0.1)) +p_smoother = Key_C('smoother', Enm_P('val1', items={'GaussSeidel', 'symGaussSeidel'}, default='GaussSeidel')) + +# Create the 'p' node and add the Key_C objects +p_node = Node_C('p', solvers_node, None, p_solver, p_tolerance, p_relTol, p_smoother) + +# Create Key_C objects for 'pFinal' solver +pFinal_tolerance = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1, default=1e-06)) +pFinal_relTol = Key_C('relTol', Flt_P('val1', minimum=0, maximum=1, default=0)) + +# Create the 'pFinal' node and add the Key_C objects +pFinal_node = Node_C('pFinal', solvers_node, None, pFinal_tolerance, pFinal_relTol) + +# Create Key_C objects for the generic solver +generic_solver = Key_C('solver', Enm_P('val1', items={'GAMG', 'PCG', 'PBiCGStab', 'smoothSolver'}, default='smoothSolver')) +generic_smoother = Key_C('smoother', Enm_P('val1', items={'GaussSeidel', 'symGaussSeidel'}, default='GaussSeidel')) +generic_tolerance = Key_C('tolerance', Flt_P('val1', minimum=0, maximum=1, default=1e-05)) +generic_relTol = Key_C('relTol', Flt_P('val1', minimum=0, maximum=1, default=0)) + +# Create the generic solver node and add the Key_C objects +generic_node = Node_C('"(U|k|epsilon|omega|R|nuTilda).*"', solvers_node, None, generic_solver, generic_smoother, generic_tolerance, generic_relTol) + +# Create the 'PIMPLE' node +PIMPLE_node = Node_C('PIMPLE', root, None) + +# Create Key_C objects for PIMPLE +nCorrectors = Key_C('nCorrectors', Int_P('int_prop_1', minimum=0, maximum=10, default=2)) +nNonOrthogonalCorrectors = Key_C('nNonOrthogonalCorrectors', Int_P('int_prop_2', minimum=0, maximum=10, default=0)) +pRefCell = Key_C('pRefCell', Int_P('int_prop_3', minimum=0, maximum=10000, default=0)) +pRefValue = Key_C('pRefValue', Flt_P('float_prop_1', minimum=-1e10, maximum=1e10, default=0)) + +# Add Key_C objects to the PIMPLE node +PIMPLE_node.add_data(nCorrectors) +PIMPLE_node.add_data(nNonOrthogonalCorrectors) +PIMPLE_node.add_data(pRefCell) +PIMPLE_node.add_data(pRefValue) + +# Display the tree structure +show_tree(root) \ No newline at end of file diff --git a/converter/pyvnt_package/test.py b/converter/pyvnt_package/test.py new file mode 100644 index 0000000..70eee53 --- /dev/null +++ b/converter/pyvnt_package/test.py @@ -0,0 +1,6 @@ +from converter import read +from pyvnt import show_tree + +tree_node = read('/opt/openfoam11/tutorials/solidDisplacement/beamEndLoad/system/fvSolution') + +show_tree(tree_node) \ No newline at end of file -- cgit