diff --git a/.fz/calculators/Cast3m.sh b/.fz/calculators/Cast3m.sh index 9f35633..faf8391 100755 --- a/.fz/calculators/Cast3m.sh +++ b/.fz/calculators/Cast3m.sh @@ -43,7 +43,7 @@ if [ -z "$DGIBI_FILE" ]; then fi # Validate the .dgibi file path (basic validation) -if [[ ! "$DGIBI_FILE" =~ ^[a-zA-Z0-9._/ -]+$ ]]; then +if [[ ! "$DGIBI_FILE" =~ ^[a-zA-Z0-9./_-]+$ ]]; then echo "Error: Invalid characters in .dgibi filename" exit 1 fi diff --git a/.fz/calculators/localhost.json b/.fz/calculators/localhost_Cast3m.json similarity index 100% rename from .fz/calculators/localhost.json rename to .fz/calculators/localhost_Cast3m.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e8f8776 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,128 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + +jobs: + test: + name: Test Plugin Structure + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install fz framework + run: | + pip install git+https://github.com/Funz/fz.git + + - name: Validate JSON files + run: | + echo "Validating model files..." + for f in .fz/models/*.json; do + echo " Checking $f" + python -m json.tool "$f" > /dev/null + done + echo "Validating calculator config files..." + for f in .fz/calculators/*.json; do + echo " Checking $f" + python -m json.tool "$f" > /dev/null + done + + - name: Check shell script syntax + run: | + echo "Checking shell scripts..." + for f in .fz/calculators/*.sh; do + echo " Checking $f" + bash -n "$f" + done + + - name: Test fzi (parse variables) + run: | + python -c " + import fz + variables = fz.fzi('examples/Cast3m/poutre_parametric.dgibi', 'Cast3m') + print(f'Variables found: {list(variables.keys())}') + assert 'long' in variables, 'Variable long not found' + assert 'haut' in variables, 'Variable haut not found' + print('✓ fzi test passed') + " + + - name: Test fzc (compile input) + run: | + python -c " + import fz + import tempfile + import os + with tempfile.TemporaryDirectory() as tmpdir: + fz.fzc('examples/Cast3m/poutre_parametric.dgibi', {'long': 0.30, 'haut': 0.001, 'larg': 0.01, 'F': 1.0}, 'Cast3m', output_dir=tmpdir) + # Find compiled file in subdirectory + compiled = None + for root, dirs, files in os.walk(tmpdir): + if 'poutre_parametric.dgibi' in files: + compiled = os.path.join(root, 'poutre_parametric.dgibi') + break + assert compiled is not None, 'Compiled file not created' + with open(compiled) as f: + content = f.read() + assert '0.30' in content or '0.3' in content, 'Variable not substituted' + print('✓ fzc test passed') + " + + - name: Run plugin tests + run: | + python tests/test_plugin.py + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check shell scripts are executable + run: | + for f in .fz/calculators/*.sh; do + if [ ! -x "$f" ]; then + echo "Error: $f is not executable" + exit 1 + fi + done + + docs: + name: Check Documentation + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check required documentation files + run: | + for f in README.md LICENSE; do + if [ ! -f "$f" ]; then + echo "Error: Missing $f" + exit 1 + fi + echo "✓ $f exists" + done + + - name: Check example files + run: | + if [ ! -f "examples/Cast3m/poutre_parametric.dgibi" ]; then + echo "Error: Missing example input file" + exit 1 + fi + echo "✓ Example files present" diff --git a/.gitignore b/.gitignore index 49075bc..604454b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,22 @@ +# Results and temporary files +results/ +*.out +*.msg +*.html +PID + # Python __pycache__/ *.py[cod] *$py.class *.so .Python -env/ venv/ ENV/ -*.egg-info/ -dist/ -build/ +.venv -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# Test results -results/ -compiled/ -*.out -*.log +# fz temporary files +.fz/tmp/ # Cast3m specific *.hermes diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c96bc41 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2025, Funz Contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 9d7e826..d04e184 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,38 @@ -# fz-cast3m +# fz-Cast3m -Cast3m plugin for the new Funz fz framework. +A [Funz](https://github.com/Funz/fz) plugin for Cast3m finite element software. + +Cast3m is a finite element software developed by CEA (French Alternative Energies and Atomic Energy Commission) for structural and fluid mechanics simulations. This plugin enables the Funz fz framework to run parametric studies with Cast3m. This repository contains the port of the [old Cast3m plugin](https://github.com/Funz/plugin-cast3m) to the new [fz framework](https://github.com/Funz/fz). -## Overview +## Features + +### Input Syntax +- **Variable syntax**: `$(variable_name)` +- **Formula syntax**: `%(formula)` +- **Comment character**: `*` -Cast3m is a finite element software developed by CEA (French Alternative Energies and Atomic Energy Commission) for structural and fluid mechanics simulations. This plugin enables the fz framework to: +### Supported Output Variables -- Parse Cast3m input files (`.dgibi` format) -- Identify variables using `$` prefix with `()` delimiters -- Execute Cast3m calculations -- Extract output variables from results +The Cast3m plugin extracts these output variables: +- **MESS variables**: Scalar values from `MESS 'varname=' varname;` commands +- **Text files**: Numeric data from `SORT 'CHAI'` outputs (*.txt) +- **CSV files**: Tabular data from `SORT 'EXCE'` outputs (*.csv) ## Installation -### Prerequisites +This plugin requires the [Funz/fz](https://github.com/Funz/fz) framework and Cast3m software. -1. Install the fz framework: ```bash -pip install funz-fz -``` +# Install fz framework +pip install git+https://github.com/Funz/fz.git -2. Install Cast3m (castem2000 or cast3m executable must be in PATH) - -### Using this Plugin +# Ensure Cast3m is installed and in PATH +# (castem2000 or cast3m executable) +``` -Clone this repository to use the Cast3m model and calculator: +Clone this repository to use the plugin: ```bash git clone https://github.com/Funz/fz-cast3m.git @@ -35,15 +41,50 @@ cd fz-cast3m ## Usage -### Input File Format +### With fz Python API + +```python +import fz + +# Example: Run parametric study with Cast3m +results = fz.fzr( + input_path="examples/Cast3m/poutre_parametric.dgibi", + input_variables={ + "long": [0.20, 0.25, 0.30, 0.35, 0.40], + "haut": [0.001, 0.002], + "larg": 0.01, + "F": 1.0 + }, + model="Cast3m", + calculators="localhost_Cast3m", + results_dir="beam_study" +) + +print(results[['long', 'haut', 'larg', 'F', 'dep_P2']]) +``` -Cast3m uses `.dgibi` files with the following syntax: +### Directory Structure -- **Variables**: `$(variable_name)` - Will be substituted by fz -- **Comments**: Lines starting with `*` -- **Output variables**: Defined using `MESS 'varname=' varname;` syntax +``` +your_project/ +├── examples/ +│ └── Cast3m/ +│ ├── poutre.dgibi # Simple beam example +│ ├── poutre_parametric.dgibi # Parametric beam example +│ └── outvar.dgibi # Output variables example +├── .fz/ +│ ├── models/ +│ │ └── Cast3m.json # Model configuration +│ └── calculators/ +│ ├── Cast3m.sh # Calculator script +│ └── localhost_Cast3m.json # Local calculator config +├── tests/ +│ └── test_plugin.py # Test suite +└── results/ # Generated by fz +``` + +## Example Input File -Example input file (`poutre_parametric.dgibi`): ``` * POUTRE CONSOLE EN FLEXION SIMPLE OPTI DIME 2 ELEM SEG2; @@ -60,148 +101,116 @@ MESS 'dep_P2=' dep_P2; FIN; ``` -### Parse Input Variables +In this example: +- `$(long)`, `$(haut)`, `$(larg)`, and `$(F)` are variable parameters that will be substituted by fz +- Lines starting with `*` are comments +- `MESS 'dep_P2=' dep_P2;` defines an output variable + +## Creating Your Own Plugin + +This repository can serve as a template for creating Cast3m-based studies: + +1. **Clone this repository** as a starting point +2. **Customize input files**: + - Add your `.dgibi` files to `examples/Cast3m/` + - Use `$(variable)` syntax for parameters + - Use `MESS 'varname=' varname;` for outputs +3. **Run parametric studies**: + - Use fz Python API or command-line tools + - Results are automatically extracted from Cast3m outputs + +### Model Configuration + +The model JSON file (`.fz/models/Cast3m.json`) defines variable syntax and output parsing: + +```json +{ + "id": "Cast3m", + "varprefix": "$", + "formulaprefix": "%", + "delim": "()", + "commentline": "*", + "output": { + "outputs": "python3 << 'PYEOF'\n... [Python code for output parsing] ...\nPYEOF\n" + } +} +``` -```bash -fzi samples/poutre_parametric.dgibi --model Cast3m +Fields: +- `id`: Unique identifier for the model +- `varprefix`: Character prefix for variables (e.g., `$` for `$(x)`) +- `formulaprefix`: Character prefix for formulas +- `delim`: Delimiter characters around variable names +- `commentline`: Character(s) that start a comment line +- `output`: Shell commands that extract output values + +### Calculator Configuration + +The calculator JSON files define how to execute Cast3m: + +```json +{ + "uri": "sh://", + "models": { + "Cast3m": "bash .fz/calculators/Cast3m.sh" + } +} ``` -### Compile Input Files +- `uri`: Execution method (`sh://` for local shell) +- `models`: Mapping of model names to execution commands -```bash -fzc samples/poutre_parametric.dgibi \ - --model Cast3m \ - --variables '{"long": 0.30, "haut": 0.001, "larg": 0.01, "F": 1.0}' \ - --output compiled/ -``` +## Remote Execution -### Read Output Files +To run calculations on a remote server: -```bash -fzo results/ --model Cast3m +```python +results = fz.fzr( + input_path="examples/Cast3m/poutre_parametric.dgibi", + input_variables={"long": [0.25, 0.30, 0.35]}, + model="Cast3m", + calculators="ssh://user@server.com/bash /path/to/calculators/Cast3m.sh", + results_dir="remote_results" +) ``` -### Run Parametric Study +## Troubleshooting +### Cast3m executable not found +Ensure Cast3m (castem2000 or cast3m) is installed and in PATH: ```bash -fzr samples/poutre_parametric.dgibi \ - --model Cast3m \ - --variables '{"long": [0.25, 0.30, 0.35], "haut": 0.001, "larg": 0.01, "F": [0.5, 1.0, 1.5]}' \ - --calculator localhost \ - --results results/ +which castem2000 +# or +which cast3m ``` -## Plugin Structure - +### Output variable not found +Check that your `.dgibi` file includes output commands: ``` -.fz/ -├── models/ -│ └── Cast3m.json # Model definition with output parsing -└── calculators/ - ├── localhost.json # Local calculator configuration - └── Cast3m.sh # Cast3m execution script - -samples/ -├── poutre.dgibi # Sample beam calculation -├── poutre_parametric.dgibi # Parametric beam example -└── outvar.dgibi # Output variables example -``` - -## Model Definition - -The Cast3m model (`.fz/models/Cast3m.json`) defines: - -- **Variable syntax**: `$` prefix with `()` delimiters -- **Formula syntax**: `%` prefix with `[]` delimiters -- **Comment lines**: Lines starting with `*` -- **Output parsing**: Python scripts to extract results from: - - `castem.out`: Main output file with `MESS` variables - - `*.txt`: Text files from `SORT 'CHAI'` commands - - `*.csv`: CSV files from `SORT 'EXCE'` commands - -### Output Variables - -The plugin supports three types of output extraction: - -1. **MESS variables** (from `castem.out`): - ``` - MESS 'dep_P2=' dep_P2; - ``` - -2. **Text file outputs** (from `SORT 'CHAI'`): - ``` - OPTI SORT 'result.txt'; - SORT 'CHAI' variable; - ``` - -3. **CSV file outputs** (from `SORT 'EXCE'`): - ``` - OPTI SORT 'results.csv'; - SORT 'EXCE' table_var; - ``` - -## Output Parsing Logic - -The output parsing is implemented in Python within the model JSON file: - -- **_mess_vars**: Extracts scalar variables from `castem.out` using regex pattern matching -- **_chai_files**: Reads text files and extracts numeric values -- **_csv_files**: Parses CSV files with semicolon delimiters and returns column data as arrays - -This replicates the logic from the original Java implementation in `Cast3mIOPlugin.java`. - -## Examples - -See the `samples/` directory for example input files. - -### Simple Execution - -Run a single calculation: -```bash -cd samples -bash ../.fz/calculators/Cast3m.sh poutre.dgibi +MESS 'varname=' varname; ``` -### Parametric Study +## Running Tests -Run multiple cases with different parameters: ```bash -fzr samples/poutre_parametric.dgibi \ - --model Cast3m \ - --variables '{ - "long": [0.20, 0.25, 0.30, 0.35, 0.40], - "haut": [0.001, 0.002], - "larg": 0.01, - "F": 1.0 - }' \ - --calculator localhost \ - --results beam_study/ \ - --format table +python tests/test_plugin.py ``` -## Original Plugin Reference - -This plugin is a port of the original Cast3m plugin: -- Repository: https://github.com/Funz/plugin-cast3m -- Key file ported: `src/main/java/org/funz/Cast3m/Cast3mIOPlugin.java` - -The main difference is that the output parsing logic (previously in Java's `readOutput` method) is now implemented as Python scripts in the JSON model definition. - -## Documentation +## License -- Cast3m official website: http://www-cast3m.cea.fr/ -- fz framework: https://github.com/Funz/fz -- Original plugin: https://github.com/Funz/plugin-cast3m +BSD 3-Clause License. See [LICENSE](LICENSE) file. -## License +## Related Links -See LICENSE file (inherited from original plugin). +- [Funz/fz](https://github.com/Funz/fz) - Main framework +- [Cast3m official website](http://www-cast3m.cea.fr/) - Cast3m software +- [Original Cast3m plugin](https://github.com/Funz/plugin-cast3m) - Java-based plugin for old Funz framework -## Copyright +## Acknowledgments -Original plugin: +This plugin is a port of the original Cast3m plugin to the new fz framework. The original plugin was: - Copyright: IRSN, Paris, FRANCE - Developed by: Artenum SARL - Authors: Laurent Mallet, Arnaud Trouche -Ported to fz framework: 2024 \ No newline at end of file +Ported to fz framework: 2024-2025 \ No newline at end of file diff --git a/EXAMPLE.md b/docs/EXAMPLE.md similarity index 91% rename from EXAMPLE.md rename to docs/EXAMPLE.md index e37fe05..e4eb469 100644 --- a/EXAMPLE.md +++ b/docs/EXAMPLE.md @@ -6,7 +6,7 @@ This document demonstrates the complete workflow of using the Cast3m plugin with ```bash # Install fz -pip install funz-fz +pip install git+https://github.com/Funz/fz.git # Install Cast3m (if not already installed) # The castem2000 or cast3m executable must be in your PATH @@ -20,7 +20,7 @@ Identify variables in a parametric Cast3m input file: cd fz-cast3m python3 -c " import fz -variables = fz.fzi('samples/poutre_parametric.dgibi', 'Cast3m') +variables = fz.fzi('examples/Cast3m/poutre_parametric.dgibi', 'Cast3m') print('Variables found:', list(variables.keys())) " ``` @@ -43,7 +43,7 @@ variables = { 'larg': 0.01, 'F': 1.0 } -fz.fzc('samples/poutre_parametric.dgibi', variables, 'Cast3m', output_dir='compiled') +fz.fzc('examples/Cast3m/poutre_parametric.dgibi', variables, 'Cast3m', output_dir='compiled') print('Compiled file created in: compiled/') " ``` @@ -116,7 +116,7 @@ import json # Step 1: Parse input variables print("Step 1: Parsing input variables...") -variables = fz.fzi('samples/poutre_parametric.dgibi', 'Cast3m') +variables = fz.fzi('examples/Cast3m/poutre_parametric.dgibi', 'Cast3m') print(f"Found variables: {list(variables.keys())}") # Step 2: Define parameter grid @@ -131,7 +131,7 @@ print(f"Parameter combinations: {3 * 1 * 1 * 3} = 9 cases") # Step 3: Compile input files print("\nStep 3: Compiling input files...") -fz.fzc('samples/poutre_parametric.dgibi', param_grid, 'Cast3m', output_dir='param_study') +fz.fzc('examples/Cast3m/poutre_parametric.dgibi', param_grid, 'Cast3m', output_dir='param_study') print("Compiled files created in param_study/") # List generated directories @@ -166,7 +166,7 @@ param_grid = { # Run parametric study results = fz.fzr( - 'samples/poutre_parametric.dgibi', + 'examples/Cast3m/poutre_parametric.dgibi', param_grid, 'Cast3m', calculators=['localhost'], diff --git a/PORTING_SUMMARY.md b/docs/PORTING_SUMMARY.md similarity index 96% rename from PORTING_SUMMARY.md rename to docs/PORTING_SUMMARY.md index 4735c47..e51490b 100644 --- a/PORTING_SUMMARY.md +++ b/docs/PORTING_SUMMARY.md @@ -23,7 +23,7 @@ fz-cast3m/ │ └── calculators/ │ ├── Cast3m.sh # Execution script │ └── localhost.json # Local calculator config -├── samples/ +├── examples/Cast3m/ │ ├── poutre.dgibi # Simple example │ ├── poutre_parametric.dgibi # Parametric example │ └── outvar.dgibi # Output examples @@ -193,9 +193,9 @@ results = fz.fzr( - `.fz/models/README.md` - Design documentation - `.fz/calculators/Cast3m.sh` - Execution script - `.fz/calculators/localhost.json` - Calculator config -- `samples/poutre.dgibi` - Example 1 -- `samples/poutre_parametric.dgibi` - Example 2 (parametric) -- `samples/outvar.dgibi` - Example 3 (outputs) +- `examples/Cast3m/poutre.dgibi` - Example 1 +- `examples/Cast3m/poutre_parametric.dgibi` - Example 2 (parametric) +- `examples/Cast3m/outvar.dgibi` - Example 3 (outputs) - `README.md` - Main documentation - `EXAMPLE.md` - Usage examples - `.gitignore` - Git ignore rules diff --git a/example_usage.ipynb b/example_usage.ipynb new file mode 100644 index 0000000..6ee7998 --- /dev/null +++ b/example_usage.ipynb @@ -0,0 +1,225 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# fz-Cast3m Example Usage\n", + "\n", + "This notebook demonstrates how to use the fz-Cast3m plugin for parametric studies with Cast3m finite element software.\n", + "\n", + "## Prerequisites\n", + "\n", + "- fz framework installed: `pip install git+https://github.com/Funz/fz.git`\n", + "- Cast3m software installed (castem2000 or cast3m in PATH)\n", + "- This repository cloned" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import fz\n", + "import pandas as pd\n", + "import os\n", + "\n", + "# Change to repository root if needed\n", + "if not os.path.exists('.fz'):\n", + " os.chdir('..')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Parse Variables from Input File\n", + "\n", + "First, let's examine what variables are in our parametric beam example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Parse variables from input file\n", + "variables = fz.fzi('examples/Cast3m/poutre_parametric.dgibi', 'Cast3m')\n", + "print(\"Variables found in input file:\")\n", + "for var, info in variables.items():\n", + " print(f\" - {var}: {info}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Compile Input File with Specific Values\n", + "\n", + "Now let's compile the input file with specific parameter values:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Compile with specific values\n", + "fz.fzc(\n", + " 'examples/Cast3m/poutre_parametric.dgibi',\n", + " {'long': 0.30, 'haut': 0.001, 'larg': 0.01, 'F': 1.0},\n", + " 'Cast3m',\n", + " output_dir='compiled_example'\n", + ")\n", + "\n", + "# Show the compiled file\n", + "import glob\n", + "compiled_files = glob.glob('compiled_example/**/poutre_parametric.dgibi', recursive=True)\n", + "if compiled_files:\n", + " print(\"Compiled file:\")\n", + " with open(compiled_files[0]) as f:\n", + " for i, line in enumerate(f, 1):\n", + " print(f\"{i:3d}: {line.rstrip()}\")\n", + " if i > 15: # Show first 15 lines\n", + " print(\" ...\")\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Run Parametric Study\n", + "\n", + "Now let's run a parametric study with multiple parameter values.\n", + "\n", + "**Note**: This requires Cast3m to be installed. If Cast3m is not available, this cell will fail." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Run parametric study\n", + "# We'll vary the length and force, keeping other parameters constant\n", + "results = fz.fzr(\n", + " input_path='examples/Cast3m/poutre_parametric.dgibi',\n", + " input_variables={\n", + " 'long': [0.20, 0.25, 0.30, 0.35, 0.40], # 5 values\n", + " 'haut': 0.001, # constant\n", + " 'larg': 0.01, # constant\n", + " 'F': [0.5, 1.0, 1.5] # 3 values\n", + " },\n", + " model='Cast3m',\n", + " calculators='localhost_Cast3m',\n", + " results_dir='notebook_results'\n", + ")\n", + "\n", + "print(f\"\\nTotal number of runs: {len(results)}\")\n", + "print(\"\\nFirst few results:\")\n", + "results.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Analyze Results\n", + "\n", + "Let's analyze the relationship between parameters and outputs:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# Plot displacement vs length for different forces\n", + "fig, ax = plt.subplots(figsize=(10, 6))\n", + "\n", + "for force in results['F'].unique():\n", + " data = results[results['F'] == force].sort_values('long')\n", + " ax.plot(data['long'], data['dep_P2'], marker='o', label=f'F={force}')\n", + "\n", + "ax.set_xlabel('Length (m)')\n", + "ax.set_ylabel('Displacement dep_P2')\n", + "ax.set_title('Beam Displacement vs Length for Different Forces')\n", + "ax.legend()\n", + "ax.grid(True)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Summary Statistics\n", + "\n", + "Let's compute some summary statistics:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Summary statistics for dep_P2:\")\n", + "print(results.groupby('F')['dep_P2'].describe())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cleanup\n", + "\n", + "Remove temporary files:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import shutil\n", + "\n", + "# Remove temporary directories\n", + "for directory in ['compiled_example', 'notebook_results']:\n", + " if os.path.exists(directory):\n", + " shutil.rmtree(directory)\n", + " print(f\"Removed {directory}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/samples/outvar.dgibi b/examples/Cast3m/outvar.dgibi similarity index 100% rename from samples/outvar.dgibi rename to examples/Cast3m/outvar.dgibi diff --git a/samples/poutre.dgibi b/examples/Cast3m/poutre.dgibi similarity index 100% rename from samples/poutre.dgibi rename to examples/Cast3m/poutre.dgibi diff --git a/samples/poutre_parametric.dgibi b/examples/Cast3m/poutre_parametric.dgibi similarity index 100% rename from samples/poutre_parametric.dgibi rename to examples/Cast3m/poutre_parametric.dgibi diff --git a/tests/test_plugin.py b/tests/test_plugin.py new file mode 100755 index 0000000..f257069 --- /dev/null +++ b/tests/test_plugin.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +""" +Basic test suite for fz-Cast3m plugin. + +These tests verify the plugin structure. +They test: +- Model file validity +- Variable parsing +- Input file compilation +- Calculator configuration +""" + +import os +import json +import tempfile +import sys + + +def test_model_files(): + """Test that all model JSON files are valid and have required fields.""" + print("Testing model files...") + + models = [ + ".fz/models/Cast3m.json" + ] + + required_fields = ["id", "varprefix", "delim", "commentline", "output"] + + for model_file in models: + print(f" Checking {model_file}...", end=" ") + + # Check file exists + assert os.path.exists(model_file), f"File not found: {model_file}" + + # Load and validate JSON + with open(model_file, 'r') as f: + model = json.load(f) + + # Check required fields + for field in required_fields: + assert field in model, f"Missing field '{field}' in {model_file}" + + # Check output section has at least one variable + assert len(model["output"]) > 0, f"No output variables in {model_file}" + + print("✓") + + print(" All model files valid!\n") + + +def test_calculator_files(): + """Test that calculator JSON files are valid.""" + print("Testing calculator configuration files...") + + calculators = [ + ".fz/calculators/localhost_Cast3m.json" + ] + + for calc_file in calculators: + print(f" Checking {calc_file}...", end=" ") + + # Check file exists + assert os.path.exists(calc_file), f"File not found: {calc_file}" + + # Load and validate JSON + with open(calc_file, 'r') as f: + calc = json.load(f) + + # Check required fields + assert "uri" in calc, f"Missing 'uri' field in {calc_file}" + assert "models" in calc, f"Missing 'models' field in {calc_file}" + + print("✓") + + print(" All calculator files valid!\n") + + +def test_calculator_scripts(): + """Test that calculator shell scripts exist and are executable.""" + print("Testing calculator shell scripts...") + + scripts = [ + ".fz/calculators/Cast3m.sh" + ] + + for script_file in scripts: + print(f" Checking {script_file}...", end=" ") + + # Check file exists + assert os.path.exists(script_file), f"File not found: {script_file}" + + # Check if executable + assert os.access(script_file, os.X_OK), f"Script not executable: {script_file}" + + print("✓") + + print(" All calculator scripts valid!\n") + + +def test_example_files(): + """Test that example files exist.""" + print("Testing example files...") + + examples = [ + "examples/Cast3m/poutre.dgibi", + "examples/Cast3m/poutre_parametric.dgibi", + "examples/Cast3m/outvar.dgibi" + ] + + for example_file in examples: + print(f" Checking {example_file}...", end=" ") + assert os.path.exists(example_file), f"File not found: {example_file}" + print("✓") + + print(" All example files present!\n") + + +def test_with_fz(): + """Test integration with fz framework (if available).""" + print("Testing fz framework integration...") + + try: + import fz + print(" fz module found ✓") + + # Test parsing input file + print(" Testing fz.fzi() on poutre_parametric.dgibi...", end=" ") + variables = fz.fzi("examples/Cast3m/poutre_parametric.dgibi", "Cast3m") + assert "long" in variables, "Variable 'long' not found in parsed input" + assert "haut" in variables, "Variable 'haut' not found in parsed input" + assert "larg" in variables, "Variable 'larg' not found in parsed input" + assert "F" in variables, "Variable 'F' not found in parsed input" + print("✓") + + # Test compiling input file + print(" Testing fz.fzc() compilation...", end=" ") + with tempfile.TemporaryDirectory() as tmpdir: + fz.fzc( + "examples/Cast3m/poutre_parametric.dgibi", + {"long": 0.30, "haut": 0.001, "larg": 0.01, "F": 1.0}, + "Cast3m", + output_dir=tmpdir + ) + + # Check compiled file exists (it may be in a subdirectory) + compiled_file = None + for root, dirs, files in os.walk(tmpdir): + if "poutre_parametric.dgibi" in files: + compiled_file = os.path.join(root, "poutre_parametric.dgibi") + break + assert compiled_file is not None, "Compiled file not created" + + # Check variables were substituted + with open(compiled_file, 'r') as f: + content = f.read() + assert "0.30" in content or "0.3" in content, "Variable long not substituted" + assert "0.001" in content, "Variable haut not substituted" + assert "0.01" in content, "Variable larg not substituted" + assert "1.0" in content or "1" in content, "Variable F not substituted" + assert "$(long)" not in content, "Variable marker $(long) still present" + assert "$(haut)" not in content, "Variable marker $(haut) still present" + assert "$(larg)" not in content, "Variable marker $(larg) still present" + assert "$(F)" not in content, "Variable marker $(F) still present" + print("✓") + + print(" fz integration tests passed!\n") + + except ImportError: + print(" fz module not installed - skipping integration tests") + print(" (Install with: pip install git+https://github.com/Funz/fz.git)\n") + + +def main(): + """Run all tests.""" + print("=" * 70) + print("fz-Cast3m Plugin Test Suite") + print("=" * 70) + print() + + # Change to repository root if needed + if not os.path.exists(".fz"): + if os.path.exists("../fz-cast3m/.fz"): + os.chdir("../fz-cast3m") + else: + print("Error: Could not find .fz directory") + print("Please run this script from the fz-cast3m repository root") + return 1 + + try: + test_model_files() + test_calculator_files() + test_calculator_scripts() + test_example_files() + test_with_fz() + + print("=" * 70) + print("All tests passed! ✓") + print("=" * 70) + return 0 + + except AssertionError as e: + print(f"\n✗ Test failed: {e}") + return 1 + except Exception as e: + print(f"\n✗ Unexpected error: {e}") + import traceback + traceback.print_exc() + return 1 + + +if __name__ == "__main__": + sys.exit(main())