flowgraph 0.1.6__tar.gz → 0.1.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flowgraph-0.1.6/src/flowgraph.egg-info → flowgraph-0.1.7}/PKG-INFO +56 -3
- {flowgraph-0.1.6 → flowgraph-0.1.7}/README.md +55 -2
- {flowgraph-0.1.6 → flowgraph-0.1.7}/pyproject.toml +1 -1
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/__init__.py +33 -10
- flowgraph-0.1.7/src/flowgraph/adapters/calculator.py +120 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/controls.py +6 -8
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/cosapp.py +1 -1
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/data_types.py +12 -1
- flowgraph-0.1.7/src/flowgraph/adapters/field_gradient.py +136 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/field_ops.py +74 -7
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/files.py +3 -0
- flowgraph-0.1.7/src/flowgraph/adapters/filters.py +349 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/image_tools.py +135 -47
- flowgraph-0.1.7/src/flowgraph/adapters/mesh_analysis.py +282 -0
- flowgraph-0.1.7/src/flowgraph/adapters/mesh_composition.py +481 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/mesh_ops.py +38 -0
- flowgraph-0.1.7/src/flowgraph/adapters/paraview.py +107 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/plaid_bridge.py +23 -12
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/readers.py +9 -7
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/simple_sources.py +16 -12
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/user_code.py +7 -1
- flowgraph-0.1.7/src/flowgraph/adapters/vector_tools.py +168 -0
- flowgraph-0.1.7/src/flowgraph/adapters/workflow.py +306 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/writers.py +4 -0
- flowgraph-0.1.7/src/flowgraph/adapters/zones.py +311 -0
- flowgraph-0.1.7/src/flowgraph/application/file_dependencies.py +136 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/port_conversions.py +91 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_cli.py +8 -6
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_core.py +69 -6
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_defaults.py +15 -9
- flowgraph-0.1.7/src/flowgraph/application/workflow_execution.py +358 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_io.py +51 -8
- flowgraph-0.1.7/src/flowgraph/application/workflow_session.py +436 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_web_catalog.py +5 -1
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/core_cli.py +49 -1
- flowgraph-0.1.7/src/flowgraph/paraview_gateway.py +872 -0
- flowgraph-0.1.7/src/flowgraph/server/__init__.py +1 -0
- flowgraph-0.1.7/src/flowgraph/server/http.py +315 -0
- flowgraph-0.1.7/src/flowgraph/server/jobs.py +223 -0
- flowgraph-0.1.7/src/flowgraph/server/worker.py +36 -0
- flowgraph-0.1.7/src/flowgraph/server/workspace.py +136 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/AllFilters.json +2 -2
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/version.py +1 -1
- {flowgraph-0.1.6 → flowgraph-0.1.7/src/flowgraph.egg-info}/PKG-INFO +56 -3
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/SOURCES.txt +41 -3
- flowgraph-0.1.7/tests/test_array_nodes.py +226 -0
- flowgraph-0.1.7/tests/test_array_types.py +177 -0
- flowgraph-0.1.7/tests/test_cache_policy.py +191 -0
- flowgraph-0.1.7/tests/test_calculator.py +242 -0
- flowgraph-0.1.7/tests/test_conditional_workflow.py +243 -0
- flowgraph-0.1.7/tests/test_controls.py +123 -0
- flowgraph-0.1.7/tests/test_directory_dependencies.py +200 -0
- flowgraph-0.1.7/tests/test_extract_field.py +104 -0
- flowgraph-0.1.7/tests/test_extract_vector_value.py +109 -0
- flowgraph-0.1.7/tests/test_field_gradient.py +231 -0
- flowgraph-0.1.7/tests/test_fluency_optimization.py +119 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_image_tools.py +105 -2
- flowgraph-0.1.7/tests/test_local_server.py +536 -0
- flowgraph-0.1.7/tests/test_mesh_analysis.py +340 -0
- flowgraph-0.1.7/tests/test_mesh_composition.py +492 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_mesh_creation_tools.py +10 -0
- flowgraph-0.1.7/tests/test_node_selector.py +216 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_packaging.py +2 -2
- flowgraph-0.1.7/tests/test_paraview.py +1252 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_plaid_bridge.py +108 -6
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_release.py +9 -1
- flowgraph-0.1.7/tests/test_remove_lonely_nodes.py +112 -0
- flowgraph-0.1.7/tests/test_scalars_to_array_1d.py +104 -0
- flowgraph-0.1.7/tests/test_transfer_fields.py +122 -0
- flowgraph-0.1.7/tests/test_user_code_mesh.py +38 -0
- flowgraph-0.1.7/tests/test_vector_field_gradient.py +184 -0
- flowgraph-0.1.7/tests/test_vector_tools.py +184 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_core.py +36 -0
- flowgraph-0.1.6/tests/test_wasm_execution.py → flowgraph-0.1.7/tests/test_workflow_execution.py +239 -11
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_io.py +5 -3
- flowgraph-0.1.7/tests/test_workflow_session.py +634 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_web_catalog.py +67 -1
- flowgraph-0.1.7/tests/test_workspace_directories.py +54 -0
- flowgraph-0.1.7/tests/test_zones.py +214 -0
- flowgraph-0.1.6/src/flowgraph/adapters/filters.py +0 -182
- flowgraph-0.1.6/src/flowgraph/adapters/paraview.py +0 -42
- flowgraph-0.1.6/src/flowgraph/adapters/workflow.py +0 -189
- flowgraph-0.1.6/src/flowgraph/application/wasm_execution.py +0 -217
- flowgraph-0.1.6/tests/test_user_code_mesh.py +0 -18
- {flowgraph-0.1.6 → flowgraph-0.1.7}/LICENSE +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/setup.cfg +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/__init__.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/__main__.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/documentation.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/file_conversion.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/imagej.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/mesh_creation_tools.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/mesh_generations.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/remote_files.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/sinks.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/table_tools.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/workflow_interfaces.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/__init__.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/node_registry.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_history.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_layout.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflows.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/domain/__init__.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/domain/filepath.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/domain/mesh_document.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/examples/AllFilters.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/examples/convert_format.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/resources.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/ColorCardboard.jpg +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/FlowGraph.h5 +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/FlowGraph.stl +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/FlowGraph.xdmf +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/SimpleReadMesh.json +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/SpherePoints.csv +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/muscat-muscat-mesh-conversion.json +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/dependency_links.txt +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/entry_points.txt +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/requires.txt +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/top_level.txt +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_core_gui_boundary.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_cosapp.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_file_conversion.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_filepath.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_headless_boundary.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_imagej.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_mesh_ops.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_muscat_vtk.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_readers.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_remote_files.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_simple_sources.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_sinks.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_table_tools.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_cli.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_history.py +0 -0
- {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_writers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowgraph
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Headless typed workflow engine and execution runtime
|
|
5
5
|
Author: Felipe Bordeu
|
|
6
6
|
Maintainer: Felipe Bordeu
|
|
@@ -61,7 +61,14 @@ for compatible external clients.
|
|
|
61
61
|
> development, experimentation, and early adopter feedback. APIs and workflow
|
|
62
62
|
> behavior may change before `1.0.0`. See [`Release Policy`](docs/policies/release-policy.md).
|
|
63
63
|
|
|
64
|
-
##
|
|
64
|
+
## Incremental workflow execution
|
|
65
|
+
|
|
66
|
+
`WorkflowExecutor` remains stateless. Use `WorkflowSession` for selected targets,
|
|
67
|
+
dependency-aware cache reuse, forced runs and session lifecycle. See
|
|
68
|
+
[`Incremental execution`](doc/reference/incremental-execution.md) for the Python
|
|
69
|
+
API, node safety contracts, transport options and resource limits.
|
|
70
|
+
|
|
71
|
+
## Development setup
|
|
65
72
|
|
|
66
73
|
```bash
|
|
67
74
|
uv sync --extra dev
|
|
@@ -107,6 +114,52 @@ For examples and warning output, see
|
|
|
107
114
|
Workflows use the versioned `flowgraph-workflow` JSON format. Core validation and
|
|
108
115
|
execution are UI-independent.
|
|
109
116
|
|
|
117
|
+
### JSON execution boundary
|
|
118
|
+
|
|
119
|
+
Python clients and browser-hosted Python workers use the same entry point:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from flowgraph.application.workflow_execution import execute_workflow_json
|
|
123
|
+
|
|
124
|
+
result_json = execute_workflow_json(workflow_json)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The response includes execution order, public outputs, retained node values,
|
|
128
|
+
display previews, warnings, and structured errors. A process-local node registry
|
|
129
|
+
is reused, but each call reconstructs its workflow. This API does not provide a
|
|
130
|
+
server, authentication, cancellation, or filesystem isolation.
|
|
131
|
+
|
|
132
|
+
Mesh display previews retain scalar values or vector magnitudes in `fields` and
|
|
133
|
+
`cell_fields`. Multi-component fields additionally expose their original tuples
|
|
134
|
+
in `field_components` and `cell_field_components`, indexed by vertex or surface
|
|
135
|
+
triangle respectively, so viewers can select an individual component without
|
|
136
|
+
rerunning the workflow. These preview projections do not modify the input mesh.
|
|
137
|
+
|
|
138
|
+
The former WASM-specific execution module has been removed without a compatibility
|
|
139
|
+
alias. Browser clients must update their import and rebuild/stage their core wheel
|
|
140
|
+
together; the workflow file and execution response formats are unchanged.
|
|
141
|
+
|
|
142
|
+
## Experimental local server
|
|
143
|
+
|
|
144
|
+
The prototype branch includes an authenticated, loopback-only execution API:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
uv run flowgraph serve --workspace /absolute/path/to/existing/workspace
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Port 8765 and the allowed editor origin `https://flowgraph-web.fbw-es.com` are
|
|
151
|
+
defaults; explicit `--allow-origin` values replace that origin. The editor still
|
|
152
|
+
requires bearer authentication and browser local-network permission. Click the
|
|
153
|
+
printed **Open editor** link to pass the token and port automatically; keep the
|
|
154
|
+
link private.
|
|
155
|
+
|
|
156
|
+
It runs one workflow at a time in a cancellable native subprocess and provides
|
|
157
|
+
runtime metadata, catalog, and workspace file endpoints. Core is API-only and never
|
|
158
|
+
serves editor assets. Web and desktop clients own their GUI hosting and explicitly
|
|
159
|
+
allow their origin with `--allow-origin`. Native workflow code is **not sandboxed**.
|
|
160
|
+
See [Local server API and limitations](docs/local-server.md) for authentication,
|
|
161
|
+
request/response formats, examples, limits, and shutdown behavior.
|
|
162
|
+
|
|
110
163
|
## Package artifacts
|
|
111
164
|
|
|
112
165
|
Build the portable, pure-Python source distribution and wheel with:
|
|
@@ -129,7 +182,7 @@ Docker, operating-system package managers, and third-party mirrors are not
|
|
|
129
182
|
supported release channels at this time. See
|
|
130
183
|
[`Release Channels`](docs/policies/release-channels.md) for the release policy.
|
|
131
184
|
|
|
132
|
-
FlowGraph Core declares Python 3.11 or newer. CI covers Python 3.11
|
|
185
|
+
FlowGraph Core declares Python 3.11 or newer. CI covers Python 3.11, 3.12, 3.13, and 3.14 on
|
|
133
186
|
GitHub-hosted Ubuntu, Windows, and macOS runners; a platform/version combination
|
|
134
187
|
is supported only after its release-candidate job passes. See
|
|
135
188
|
[`Support Policy`](docs/policies/support-policy.md) for compatibility, deprecation,
|
|
@@ -9,7 +9,14 @@ for compatible external clients.
|
|
|
9
9
|
> development, experimentation, and early adopter feedback. APIs and workflow
|
|
10
10
|
> behavior may change before `1.0.0`. See [`Release Policy`](docs/policies/release-policy.md).
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Incremental workflow execution
|
|
13
|
+
|
|
14
|
+
`WorkflowExecutor` remains stateless. Use `WorkflowSession` for selected targets,
|
|
15
|
+
dependency-aware cache reuse, forced runs and session lifecycle. See
|
|
16
|
+
[`Incremental execution`](doc/reference/incremental-execution.md) for the Python
|
|
17
|
+
API, node safety contracts, transport options and resource limits.
|
|
18
|
+
|
|
19
|
+
## Development setup
|
|
13
20
|
|
|
14
21
|
```bash
|
|
15
22
|
uv sync --extra dev
|
|
@@ -55,6 +62,52 @@ For examples and warning output, see
|
|
|
55
62
|
Workflows use the versioned `flowgraph-workflow` JSON format. Core validation and
|
|
56
63
|
execution are UI-independent.
|
|
57
64
|
|
|
65
|
+
### JSON execution boundary
|
|
66
|
+
|
|
67
|
+
Python clients and browser-hosted Python workers use the same entry point:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from flowgraph.application.workflow_execution import execute_workflow_json
|
|
71
|
+
|
|
72
|
+
result_json = execute_workflow_json(workflow_json)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The response includes execution order, public outputs, retained node values,
|
|
76
|
+
display previews, warnings, and structured errors. A process-local node registry
|
|
77
|
+
is reused, but each call reconstructs its workflow. This API does not provide a
|
|
78
|
+
server, authentication, cancellation, or filesystem isolation.
|
|
79
|
+
|
|
80
|
+
Mesh display previews retain scalar values or vector magnitudes in `fields` and
|
|
81
|
+
`cell_fields`. Multi-component fields additionally expose their original tuples
|
|
82
|
+
in `field_components` and `cell_field_components`, indexed by vertex or surface
|
|
83
|
+
triangle respectively, so viewers can select an individual component without
|
|
84
|
+
rerunning the workflow. These preview projections do not modify the input mesh.
|
|
85
|
+
|
|
86
|
+
The former WASM-specific execution module has been removed without a compatibility
|
|
87
|
+
alias. Browser clients must update their import and rebuild/stage their core wheel
|
|
88
|
+
together; the workflow file and execution response formats are unchanged.
|
|
89
|
+
|
|
90
|
+
## Experimental local server
|
|
91
|
+
|
|
92
|
+
The prototype branch includes an authenticated, loopback-only execution API:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uv run flowgraph serve --workspace /absolute/path/to/existing/workspace
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Port 8765 and the allowed editor origin `https://flowgraph-web.fbw-es.com` are
|
|
99
|
+
defaults; explicit `--allow-origin` values replace that origin. The editor still
|
|
100
|
+
requires bearer authentication and browser local-network permission. Click the
|
|
101
|
+
printed **Open editor** link to pass the token and port automatically; keep the
|
|
102
|
+
link private.
|
|
103
|
+
|
|
104
|
+
It runs one workflow at a time in a cancellable native subprocess and provides
|
|
105
|
+
runtime metadata, catalog, and workspace file endpoints. Core is API-only and never
|
|
106
|
+
serves editor assets. Web and desktop clients own their GUI hosting and explicitly
|
|
107
|
+
allow their origin with `--allow-origin`. Native workflow code is **not sandboxed**.
|
|
108
|
+
See [Local server API and limitations](docs/local-server.md) for authentication,
|
|
109
|
+
request/response formats, examples, limits, and shutdown behavior.
|
|
110
|
+
|
|
58
111
|
## Package artifacts
|
|
59
112
|
|
|
60
113
|
Build the portable, pure-Python source distribution and wheel with:
|
|
@@ -77,7 +130,7 @@ Docker, operating-system package managers, and third-party mirrors are not
|
|
|
77
130
|
supported release channels at this time. See
|
|
78
131
|
[`Release Channels`](docs/policies/release-channels.md) for the release policy.
|
|
79
132
|
|
|
80
|
-
FlowGraph Core declares Python 3.11 or newer. CI covers Python 3.11
|
|
133
|
+
FlowGraph Core declares Python 3.11 or newer. CI covers Python 3.11, 3.12, 3.13, and 3.14 on
|
|
81
134
|
GitHub-hosted Ubuntu, Windows, and macOS runners; a platform/version combination
|
|
82
135
|
is supported only after its release-candidate job passes. See
|
|
83
136
|
[`Support Policy`](docs/policies/support-policy.md) for compatibility, deprecation,
|
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from flowgraph.adapters.controls import AVAILABLE_NODES as CONTROL_AVAILABLE_NODES
|
|
6
6
|
from flowgraph.adapters.cosapp import AVAILABLE_NODES as COSAPP_AVAILABLE_NODES
|
|
7
7
|
from flowgraph.adapters.documentation import AVAILABLE_NODES as DOCUMENTATION_AVAILABLE_NODES
|
|
8
|
+
from flowgraph.adapters.field_gradient import SCALAR_FIELD_GRADIENT, VECTOR_FIELD_GRADIENT
|
|
8
9
|
from flowgraph.adapters.field_ops import AVAILABLE_NODES as FIELD_OPS_AVAILABLE_NODES
|
|
9
10
|
from flowgraph.adapters.file_conversion import MESH_FILE_CONVERSION
|
|
10
11
|
from flowgraph.adapters.files import (
|
|
@@ -14,13 +15,18 @@ from flowgraph.adapters.files import (
|
|
|
14
15
|
)
|
|
15
16
|
from flowgraph.adapters.filters import (
|
|
16
17
|
CREATE_MUSCAT_ELEMENT_FILTER,
|
|
18
|
+
CREATE_MUSCAT_NODE_FILTER,
|
|
17
19
|
FILTER_MESH_WITH_MUSCAT,
|
|
20
|
+
SELECTOR_OPERATORS,
|
|
18
21
|
)
|
|
19
22
|
from flowgraph.adapters.image_tools import AVAILABLE_NODES as IMAGE_TOOLS_AVAILABLE_NODES
|
|
20
23
|
from flowgraph.adapters.imagej import AVAILABLE_NODES as IMAGEJ_AVAILABLE_NODES
|
|
24
|
+
from flowgraph.adapters.mesh_analysis import MESH_NODES, SET_MESH_FIELD
|
|
25
|
+
from flowgraph.adapters.mesh_composition import FIELD_COMPOSITION_NODES, MESH_COMPOSITION_NODES
|
|
21
26
|
from flowgraph.adapters.mesh_creation_tools import AVAILABLE_NODES as MESH_CREATION_AVAILABLE_NODES
|
|
22
27
|
from flowgraph.adapters.mesh_generations import CSV_TO_MESH
|
|
23
28
|
from flowgraph.adapters.mesh_ops import AVAILABLE_NODES as MESH_OPS_AVAILABLE_NODES
|
|
29
|
+
from flowgraph.adapters.paraview import PARAVIEW_BRIDGE
|
|
24
30
|
from flowgraph.adapters.plaid_bridge import AVAILABLE_NODES as PLAID_AVAILABLE_NODES
|
|
25
31
|
from flowgraph.adapters.readers import (
|
|
26
32
|
LOAD_CSV,
|
|
@@ -31,10 +37,10 @@ from flowgraph.adapters.readers import (
|
|
|
31
37
|
)
|
|
32
38
|
from flowgraph.adapters.remote_files import DOWNLOAD_URL
|
|
33
39
|
from flowgraph.adapters.simple_sources import (
|
|
40
|
+
SET_ARRAY_1D,
|
|
34
41
|
SET_BOOLEAN,
|
|
35
42
|
SET_FLOAT,
|
|
36
43
|
SET_INT,
|
|
37
|
-
SET_LIST_FLOAT,
|
|
38
44
|
SET_LIST_INT,
|
|
39
45
|
SET_LIST_STR,
|
|
40
46
|
SET_STRING,
|
|
@@ -45,11 +51,13 @@ from flowgraph.adapters.simple_sources import (
|
|
|
45
51
|
from flowgraph.adapters.sinks import AVAILABLE_NODES as SINKS_AVAILABLE_NODES
|
|
46
52
|
from flowgraph.adapters.table_tools import AVAILABLE_NODES as TABLE_TOOLS_AVAILABLE_NODES
|
|
47
53
|
from flowgraph.adapters.user_code import AVAILABLE_NODES as USER_CODE_AVAILABLE_NODES
|
|
54
|
+
from flowgraph.adapters.vector_tools import AVAILABLE_NODES as VECTOR_TOOLS_AVAILABLE_NODES
|
|
48
55
|
from flowgraph.adapters.workflow import AVAILABLE_NODES as WORKFLOW_AVAILABLE_NODES
|
|
49
56
|
from flowgraph.adapters.workflow_interfaces import (
|
|
50
57
|
AVAILABLE_NODES as WORKFLOW_INTERFACE_AVAILABLE_NODES,
|
|
51
58
|
)
|
|
52
59
|
from flowgraph.adapters.writers import WRITE_MESHIO, WRITE_MESHLANE, WRITE_MUSCAT, WRITE_TABLE
|
|
60
|
+
from flowgraph.adapters.zones import AVAILABLE_NODES as ZONE_AVAILABLE_NODES
|
|
53
61
|
from flowgraph.application.workflow_core import NodeDefinition
|
|
54
62
|
|
|
55
63
|
|
|
@@ -99,10 +107,13 @@ ADAPTERS = AdapterCatalog(
|
|
|
99
107
|
AdapterGroup(
|
|
100
108
|
"Inputs",
|
|
101
109
|
subgroups=(
|
|
102
|
-
AdapterGroup(
|
|
110
|
+
AdapterGroup(
|
|
111
|
+
"Scalars",
|
|
112
|
+
(SET_STRING, SET_BOOLEAN, SET_INT, SET_FLOAT) + CONTROL_AVAILABLE_NODES,
|
|
113
|
+
),
|
|
103
114
|
AdapterGroup(
|
|
104
115
|
"Lists/Vectors",
|
|
105
|
-
(SET_LIST_STR, SET_LIST_INT,
|
|
116
|
+
(SET_LIST_STR, SET_LIST_INT, SET_ARRAY_1D, SET_VEC2D, SET_VEC2DI, SET_VEC3D),
|
|
106
117
|
),
|
|
107
118
|
AdapterGroup(
|
|
108
119
|
"Files",
|
|
@@ -117,24 +128,36 @@ ADAPTERS = AdapterCatalog(
|
|
|
117
128
|
"Readers",
|
|
118
129
|
(LOAD_MUSCAT, LOAD_MESHIO, LOAD_MESHLANE, LOAD_CSV, READ_TABLE),
|
|
119
130
|
),
|
|
120
|
-
AdapterGroup("Mesh
|
|
121
|
-
AdapterGroup("Mesh Creation", MESH_CREATION_AVAILABLE_NODES),
|
|
131
|
+
AdapterGroup("Mesh Creation", (CSV_TO_MESH,) + MESH_CREATION_AVAILABLE_NODES),
|
|
122
132
|
AdapterGroup(
|
|
123
133
|
"Filters",
|
|
124
|
-
(
|
|
134
|
+
(
|
|
135
|
+
CREATE_MUSCAT_ELEMENT_FILTER,
|
|
136
|
+
CREATE_MUSCAT_NODE_FILTER,
|
|
137
|
+
FILTER_MESH_WITH_MUSCAT,
|
|
138
|
+
)
|
|
139
|
+
+ SELECTOR_OPERATORS,
|
|
140
|
+
subgroups=(AdapterGroup("Zones", ZONE_AVAILABLE_NODES),),
|
|
141
|
+
),
|
|
142
|
+
AdapterGroup(
|
|
143
|
+
"Mesh Ops", MESH_OPS_AVAILABLE_NODES + MESH_NODES + MESH_COMPOSITION_NODES
|
|
144
|
+
),
|
|
145
|
+
AdapterGroup(
|
|
146
|
+
"Field Ops",
|
|
147
|
+
FIELD_OPS_AVAILABLE_NODES
|
|
148
|
+
+ (SET_MESH_FIELD, SCALAR_FIELD_GRADIENT, VECTOR_FIELD_GRADIENT)
|
|
149
|
+
+ FIELD_COMPOSITION_NODES,
|
|
125
150
|
),
|
|
126
|
-
AdapterGroup("Mesh Ops", MESH_OPS_AVAILABLE_NODES),
|
|
127
|
-
AdapterGroup("Field Ops", FIELD_OPS_AVAILABLE_NODES),
|
|
128
151
|
AdapterGroup("File Conversion", (MESH_FILE_CONVERSION,)),
|
|
129
152
|
AdapterGroup("Writers", (WRITE_MUSCAT, WRITE_MESHLANE, WRITE_MESHIO, WRITE_TABLE)),
|
|
130
153
|
),
|
|
131
154
|
),
|
|
132
|
-
AdapterGroup("Controls", CONTROL_AVAILABLE_NODES),
|
|
133
155
|
AdapterGroup("Image Tools", IMAGE_TOOLS_AVAILABLE_NODES),
|
|
134
156
|
AdapterGroup("Table Tools", TABLE_TOOLS_AVAILABLE_NODES),
|
|
157
|
+
AdapterGroup("Vector Tools", VECTOR_TOOLS_AVAILABLE_NODES),
|
|
135
158
|
AdapterGroup("Code", USER_CODE_AVAILABLE_NODES + IMAGEJ_AVAILABLE_NODES),
|
|
136
159
|
AdapterGroup("Workflow", WORKFLOW_INTERFACE_AVAILABLE_NODES + WORKFLOW_AVAILABLE_NODES),
|
|
137
|
-
AdapterGroup("Sinks", SINKS_AVAILABLE_NODES),
|
|
160
|
+
AdapterGroup("Sinks", SINKS_AVAILABLE_NODES + (PARAVIEW_BRIDGE,)),
|
|
138
161
|
AdapterGroup("Doc", DOCUMENTATION_AVAILABLE_NODES),
|
|
139
162
|
AdapterGroup(
|
|
140
163
|
"External Tools",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Trusted Python-expression calculator with positional and upstream-name inputs."""
|
|
2
|
+
|
|
3
|
+
import builtins
|
|
4
|
+
import keyword
|
|
5
|
+
import re
|
|
6
|
+
from collections import Counter
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from flowgraph.application.workflow_core import (
|
|
13
|
+
ExecContext,
|
|
14
|
+
NodeDefinition,
|
|
15
|
+
NodeInstance,
|
|
16
|
+
NodeRegistry,
|
|
17
|
+
ParameterDefinition,
|
|
18
|
+
PortDefinition,
|
|
19
|
+
PortDirection,
|
|
20
|
+
WorkflowEdge,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from .data_types import ANY, ParameterKind
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _numeric_value(value: Any, label: str) -> Any:
|
|
27
|
+
"""Normalize lists and expose numeric array storage through a read-only view."""
|
|
28
|
+
if isinstance(value, list):
|
|
29
|
+
value = np.asarray(value)
|
|
30
|
+
if isinstance(value, np.ndarray):
|
|
31
|
+
if value.dtype.kind not in "buifc":
|
|
32
|
+
raise TypeError(f"Calculator {label} must contain numeric values")
|
|
33
|
+
view = value.view(np.ndarray)
|
|
34
|
+
view.flags.writeable = False
|
|
35
|
+
return view
|
|
36
|
+
if isinstance(value, (bool, int, float, complex, np.number, np.bool_)):
|
|
37
|
+
if np.asarray(value).dtype.kind not in "buifc":
|
|
38
|
+
raise TypeError(f"Calculator {label} must be numeric")
|
|
39
|
+
return value.item() if isinstance(value, np.generic) else value
|
|
40
|
+
raise TypeError(f"Calculator {label} must be a numeric scalar or NumPy array")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _calculate(
|
|
44
|
+
inputs: Mapping[str, Any],
|
|
45
|
+
parameters: Mapping[str, Any],
|
|
46
|
+
input_edges: Mapping[str, WorkflowEdge],
|
|
47
|
+
) -> Mapping[str, Any]:
|
|
48
|
+
"""Evaluate one trusted expression; this is intentionally not a sandbox."""
|
|
49
|
+
formula = inputs.get("formula", parameters.get("formula", "0"))
|
|
50
|
+
if not isinstance(formula, str) or not formula.strip():
|
|
51
|
+
raise ValueError("Calculator formula must be a non-empty Python expression")
|
|
52
|
+
|
|
53
|
+
namespace = {
|
|
54
|
+
name: _numeric_value(inputs[name], f"input {name!r}")
|
|
55
|
+
for index in range(1, 126)
|
|
56
|
+
if (name := f"input_{index}") in inputs
|
|
57
|
+
}
|
|
58
|
+
data_names = frozenset(namespace)
|
|
59
|
+
names = Counter(edge.source_port for name, edge in input_edges.items() if name in data_names)
|
|
60
|
+
for name, edge in input_edges.items():
|
|
61
|
+
alias = edge.source_port
|
|
62
|
+
if (
|
|
63
|
+
name in data_names
|
|
64
|
+
and names[alias] == 1
|
|
65
|
+
and alias.isidentifier()
|
|
66
|
+
and not keyword.iskeyword(alias)
|
|
67
|
+
and not re.fullmatch(r"input_[0-9]+", alias)
|
|
68
|
+
and alias not in {"np", "__builtins__"}
|
|
69
|
+
and not hasattr(builtins, alias)
|
|
70
|
+
):
|
|
71
|
+
namespace[alias] = namespace[name]
|
|
72
|
+
namespace.update(np=np, __builtins__=builtins.__dict__)
|
|
73
|
+
try:
|
|
74
|
+
# One shared namespace also makes aliases available inside comprehensions/lambdas.
|
|
75
|
+
result = eval(compile(formula.strip(), "<calculator formula>", "eval"), namespace)
|
|
76
|
+
except Exception as error:
|
|
77
|
+
raise ValueError(f"Calculator formula failed ({type(error).__name__}): {error}") from error
|
|
78
|
+
if isinstance(result, list):
|
|
79
|
+
raise TypeError("Calculator result must be a numeric scalar or NumPy array; use np.array")
|
|
80
|
+
return {"result": _numeric_value(result, "result")}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _calculator(
|
|
84
|
+
inputs: Mapping[str, Any],
|
|
85
|
+
parameters: Mapping[str, Any],
|
|
86
|
+
_exec_context: ExecContext | None = None,
|
|
87
|
+
) -> Mapping[str, Any]:
|
|
88
|
+
"""Evaluate using numbered names when called without workflow provenance."""
|
|
89
|
+
return _calculate(inputs, parameters, {})
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _calculator_instance(
|
|
93
|
+
_instance: NodeInstance,
|
|
94
|
+
inputs: Mapping[str, Any],
|
|
95
|
+
parameters: Mapping[str, Any],
|
|
96
|
+
_registry: NodeRegistry,
|
|
97
|
+
input_edges: Mapping[str, WorkflowEdge],
|
|
98
|
+
_exec_context: ExecContext,
|
|
99
|
+
) -> Mapping[str, Any]:
|
|
100
|
+
"""Resolve optional aliases from actual upstream output port names."""
|
|
101
|
+
return _calculate(inputs, parameters, input_edges)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
CALCULATOR = NodeDefinition(
|
|
105
|
+
id="calculator",
|
|
106
|
+
label="Calculator",
|
|
107
|
+
icon="mdi-calculator",
|
|
108
|
+
description="Evaluate a trusted Python expression with np, numbered inputs and unique aliases.",
|
|
109
|
+
requirements=("py:numpy",),
|
|
110
|
+
ports=(
|
|
111
|
+
PortDefinition("input", PortDirection.INPUT, ANY, "Input", required=False, expandable=125),
|
|
112
|
+
PortDefinition("result", PortDirection.OUTPUT, ANY, "Result"),
|
|
113
|
+
),
|
|
114
|
+
parameters=(ParameterDefinition("formula", ParameterKind.TEXT, "Formula", "0", "input_1 * 2"),),
|
|
115
|
+
executor=_calculator,
|
|
116
|
+
edge_aware_instance_executor=_calculator_instance,
|
|
117
|
+
cache_policy="always",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
__all__ = ["CALCULATOR"]
|
|
@@ -35,17 +35,15 @@ def _slider_bounds(
|
|
|
35
35
|
value_type = "integer" if integer else "number"
|
|
36
36
|
raise TypeError(f"The slider min, max, and value must be {value_type}s")
|
|
37
37
|
|
|
38
|
-
if integer:
|
|
39
|
-
if minimum >= maximum:
|
|
40
|
-
raise ValueError("The slider minimum must be less than its maximum")
|
|
41
|
-
else:
|
|
38
|
+
if not integer:
|
|
42
39
|
minimum = float(minimum)
|
|
43
40
|
maximum = float(maximum)
|
|
44
41
|
value = float(value)
|
|
45
42
|
if not all(math.isfinite(item) for item in (minimum, maximum, value)):
|
|
46
43
|
raise ValueError("The slider min, max, and value must be finite")
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
|
|
45
|
+
if minimum > maximum:
|
|
46
|
+
raise ValueError("The slider minimum must be less than or equal to its maximum")
|
|
49
47
|
|
|
50
48
|
if not minimum <= value <= maximum:
|
|
51
49
|
raise ValueError("The slider value must be between its minimum and maximum")
|
|
@@ -73,7 +71,7 @@ FLOAT_SLIDER = NodeDefinition(
|
|
|
73
71
|
parameters=(
|
|
74
72
|
ParameterDefinition("min", ParameterKind.FLOAT, "Minimum", 0.0, "0.0"),
|
|
75
73
|
ParameterDefinition("max", ParameterKind.FLOAT, "Maximum", 1.0, "1.0"),
|
|
76
|
-
ParameterDefinition("value", ParameterKind.FLOAT, "Value", 0.5, "0.5"
|
|
74
|
+
ParameterDefinition("value", ParameterKind.FLOAT, "Value", 0.5, "0.5"),
|
|
77
75
|
),
|
|
78
76
|
)
|
|
79
77
|
|
|
@@ -99,7 +97,7 @@ INT_SLIDER = NodeDefinition(
|
|
|
99
97
|
parameters=(
|
|
100
98
|
ParameterDefinition("min", ParameterKind.INTEGER, "Minimum", 0, "0"),
|
|
101
99
|
ParameterDefinition("max", ParameterKind.INTEGER, "Maximum", 100, "100"),
|
|
102
|
-
ParameterDefinition("value", ParameterKind.INTEGER, "Value", 50, "50"
|
|
100
|
+
ParameterDefinition("value", ParameterKind.INTEGER, "Value", 50, "50"),
|
|
103
101
|
),
|
|
104
102
|
)
|
|
105
103
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
+
from Muscat.ImplicitGeometry.ImplicitGeometryBase import ImplicitGeometryBase
|
|
4
5
|
from Muscat.LinAlg.Transform import Transform
|
|
5
|
-
from Muscat.MeshContainers.Filters.FilterObjects import ElementFilter
|
|
6
|
+
from Muscat.MeshContainers.Filters.FilterObjects import ElementFilter, NodeFilter
|
|
6
7
|
from Muscat.MeshContainers.Filters.FilterOperators import FilterOperatorBase
|
|
7
8
|
from PIL.Image import Image
|
|
8
9
|
|
|
@@ -11,6 +12,9 @@ from flowgraph.domain.filepath import FilePath
|
|
|
11
12
|
from flowgraph.domain.mesh_document import MeshDocument
|
|
12
13
|
|
|
13
14
|
ANY = DataType("any", "Any", object)
|
|
15
|
+
ARRAY = DataType("array", "Array", np.ndarray)
|
|
16
|
+
ARRAY_1D = DataType("array-1d", "1D Array", np.ndarray)
|
|
17
|
+
ARRAY_2D = DataType("array-2d", "2D Array", np.ndarray)
|
|
14
18
|
STRING = DataType("string", "Text", str)
|
|
15
19
|
STR_SELECT = DataType("str", "select str", str)
|
|
16
20
|
BOOLEAN = DataType("boolean", "Boolean", bool)
|
|
@@ -18,6 +22,8 @@ INTEGER = DataType("integer", "Integer", int)
|
|
|
18
22
|
FLOAT = DataType("float", "Float", float)
|
|
19
23
|
LIST_STR = DataType("list[str]", "List Text", list[str])
|
|
20
24
|
LIST_INT = DataType("list[int]", "List Ints", list[int])
|
|
25
|
+
# JSON-friendly parameter editors and APIs explicitly requiring Python lists.
|
|
26
|
+
# Numeric data ports should normally use ARRAY_1D instead.
|
|
21
27
|
LIST_FLOAT = DataType("list[float]", "List Floats", list[float])
|
|
22
28
|
LIST_ANY = DataType("list[any]", "List Any", list)
|
|
23
29
|
FILE = DataType("filename", "FilePath", FilePath)
|
|
@@ -28,11 +34,16 @@ MESH_DOCUMENT = DataType("mesh-document", "Mesh document", MeshDocument)
|
|
|
28
34
|
TABLE_DOCUMENT = DataType("table-document", "Table", dict)
|
|
29
35
|
IMAGE = DataType("image", "Image", Image)
|
|
30
36
|
IMAGEJ = DataType("imagej", "ImageJ NumPy", np.ndarray)
|
|
37
|
+
MUSCAT_ZONE = DataType("muscat-zone", "Zone", ImplicitGeometryBase)
|
|
31
38
|
MUSCAT_ELEMENT_FILTER = DataType(
|
|
32
39
|
"filter-like",
|
|
33
40
|
"Selector",
|
|
34
41
|
(ElementFilter, FilterOperatorBase),
|
|
35
42
|
)
|
|
43
|
+
# Element filters also implement GetNodesIndices and can serve as nodal filters.
|
|
44
|
+
MUSCAT_NODE_FILTER = DataType(
|
|
45
|
+
"node-filter-like", "Node Selector", (NodeFilter, ElementFilter, FilterOperatorBase)
|
|
46
|
+
)
|
|
36
47
|
VEC3D = DataType("VEC3D", "3D Vector", list[float])
|
|
37
48
|
VEC2D = DataType("VEC2D", "2D Vector", list[float])
|
|
38
49
|
VEC3DSTR = DataType("VEC3DSTR", "3D Text Vector", list[str])
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Scalar/vector nodal derivatives using Muscat's FE/IP field representations."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from Muscat.FE.Fields.FieldTools import NodeFieldToFEField, TransferFEFieldToIPField
|
|
5
|
+
from Muscat.MeshContainers import ElementsDescription as ED
|
|
6
|
+
from Muscat.MeshContainers.Filters.FilterObjects import ElementFilter
|
|
7
|
+
|
|
8
|
+
from flowgraph.adapters.data_types import (
|
|
9
|
+
ARRAY_2D,
|
|
10
|
+
MESH_DOCUMENT,
|
|
11
|
+
MUSCAT_ELEMENT_FILTER,
|
|
12
|
+
ParameterKind,
|
|
13
|
+
)
|
|
14
|
+
from flowgraph.adapters.mesh_analysis import _editable_elements
|
|
15
|
+
from flowgraph.adapters.mesh_composition import _field_name, _fields, _publish
|
|
16
|
+
from flowgraph.application.workflow_core import (
|
|
17
|
+
NodeDefinition,
|
|
18
|
+
ParameterDefinition,
|
|
19
|
+
ParameterOption,
|
|
20
|
+
PortDefinition,
|
|
21
|
+
PortDirection,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _gradient(inputs, parameters, _exec_context=None, *, vector=False):
|
|
26
|
+
"""Differentiate a scalar/vector node field on selected elements using Muscat."""
|
|
27
|
+
source = inputs["mesh"].mesh
|
|
28
|
+
name = _field_name(parameters.get("fieldName", ""))
|
|
29
|
+
if name not in source.nodeFields:
|
|
30
|
+
raise ValueError(f"{'Vector' if vector else 'Scalar'} node field {name!r} does not exist")
|
|
31
|
+
scalar = source.nodeFields[name]
|
|
32
|
+
shape_valid = (
|
|
33
|
+
scalar.ndim == 2
|
|
34
|
+
and scalar.shape[0] == source.GetNumberOfNodes()
|
|
35
|
+
and scalar.shape[1] in (2, 3)
|
|
36
|
+
if vector
|
|
37
|
+
else scalar.shape in ((source.GetNumberOfNodes(),), (source.GetNumberOfNodes(), 1))
|
|
38
|
+
)
|
|
39
|
+
if not shape_valid or scalar.dtype.kind not in "iuf" or not np.isfinite(scalar).all():
|
|
40
|
+
kind = (
|
|
41
|
+
"vector node field of shape (N, 2) or (N, 3)"
|
|
42
|
+
if vector
|
|
43
|
+
else "scalar node field of shape (N,) or (N, 1)"
|
|
44
|
+
)
|
|
45
|
+
raise ValueError(f"Source must be a finite real {kind}")
|
|
46
|
+
entity = parameters.get("entity", "node")
|
|
47
|
+
destination = _fields(source, entity)
|
|
48
|
+
target = _field_name(parameters.get("targetName", "") or f"{name}_gradient")
|
|
49
|
+
if target in destination and not parameters.get("overwrite", False):
|
|
50
|
+
raise ValueError(f"Destination field {target!r} already exists")
|
|
51
|
+
|
|
52
|
+
# Muscat FE routines cache numbering/offset metadata. Detach containers, not
|
|
53
|
+
# geometry/fields, so backend bookkeeping cannot change the upstream mesh.
|
|
54
|
+
mesh = _editable_elements(source)
|
|
55
|
+
selector = inputs.get("filter")
|
|
56
|
+
if selector is None:
|
|
57
|
+
selector = ElementFilter(dimensionality=mesh.GetElementsDimensionality())
|
|
58
|
+
dimensions = {ED.dimensionality[s.elementType] for s in selector(mesh) if s.Size()}
|
|
59
|
+
if len(dimensions) > 1 or 0 in dimensions:
|
|
60
|
+
raise ValueError("Gradient requires a single element dimension of 1, 2, or 3")
|
|
61
|
+
count = mesh.GetNumberOfNodes() if entity == "node" else mesh.GetNumberOfElements()
|
|
62
|
+
dimension = mesh.GetPointsDimensionality()
|
|
63
|
+
components = scalar.shape[1] if vector else 1
|
|
64
|
+
values = np.zeros((count, components * dimension))
|
|
65
|
+
if dimensions:
|
|
66
|
+
fields = NodeFieldToFEField(mesh, {"source": scalar if vector else scalar.reshape(-1)})
|
|
67
|
+
for component in range(components):
|
|
68
|
+
field = fields[f"source_{component}" if vector else "source"]
|
|
69
|
+
for axis in range(dimension):
|
|
70
|
+
derivative = TransferFEFieldToIPField(field, der=axis, elementFilter=selector)
|
|
71
|
+
column = component * dimension + axis
|
|
72
|
+
if entity == "element":
|
|
73
|
+
values[:, column] = derivative.GetCellRepresentation()
|
|
74
|
+
else:
|
|
75
|
+
values[:, column] = (
|
|
76
|
+
derivative.GetIpFieldRepresentation().GetPointRepresentation(
|
|
77
|
+
elementFilter=selector
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
_fields(mesh, entity)[target] = values
|
|
81
|
+
document = _publish(mesh)
|
|
82
|
+
return {"mesh": document, "gradient": _fields(document.mesh, entity)[target]}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _vector_gradient(inputs, parameters, _exec_context=None):
|
|
86
|
+
"""Compute component-major vector derivatives using Muscat, at nodes or cells."""
|
|
87
|
+
return _gradient(inputs, parameters, _exec_context, vector=True)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
SCALAR_FIELD_GRADIENT = NodeDefinition(
|
|
91
|
+
id="scalar-field-gradient",
|
|
92
|
+
label="Scalar Field Gradient",
|
|
93
|
+
icon="mdi-vector-line",
|
|
94
|
+
description="Compute a scalar node field gradient on selected elements, at nodes or cells.",
|
|
95
|
+
requirements=("py:muscat", "py:numpy"),
|
|
96
|
+
ports=(
|
|
97
|
+
PortDefinition("mesh", PortDirection.INPUT, MESH_DOCUMENT, "Mesh"),
|
|
98
|
+
PortDefinition(
|
|
99
|
+
"filter", PortDirection.INPUT, MUSCAT_ELEMENT_FILTER, "Element selector", required=False
|
|
100
|
+
),
|
|
101
|
+
PortDefinition("mesh", PortDirection.OUTPUT, MESH_DOCUMENT, "Mesh"),
|
|
102
|
+
PortDefinition("gradient", PortDirection.OUTPUT, ARRAY_2D, "Gradient"),
|
|
103
|
+
),
|
|
104
|
+
parameters=(
|
|
105
|
+
ParameterDefinition("fieldName", ParameterKind.TEXT, "Scalar node field", ""),
|
|
106
|
+
ParameterDefinition(
|
|
107
|
+
"entity",
|
|
108
|
+
ParameterKind.STR_SELECT,
|
|
109
|
+
"Output association",
|
|
110
|
+
"node",
|
|
111
|
+
options=(
|
|
112
|
+
ParameterOption("node", "Nodes"),
|
|
113
|
+
ParameterOption("element", "Cells / Elements"),
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
ParameterDefinition(
|
|
117
|
+
"targetName", ParameterKind.TEXT, "Output field (empty: source_gradient)", ""
|
|
118
|
+
),
|
|
119
|
+
ParameterDefinition("overwrite", ParameterKind.BOOLEAN, "Overwrite target", False),
|
|
120
|
+
),
|
|
121
|
+
executor=_gradient,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
VECTOR_FIELD_GRADIENT = NodeDefinition(
|
|
125
|
+
id="vector-field-gradient",
|
|
126
|
+
label="Vector Field Gradient",
|
|
127
|
+
icon="mdi-matrix",
|
|
128
|
+
description="Compute a nodal vector gradient tensor on selected elements, at nodes or cells.",
|
|
129
|
+
requirements=SCALAR_FIELD_GRADIENT.requirements,
|
|
130
|
+
ports=tuple(port for port in SCALAR_FIELD_GRADIENT.ports if not port.is_param),
|
|
131
|
+
parameters=(
|
|
132
|
+
ParameterDefinition("fieldName", ParameterKind.TEXT, "Vector node field", ""),
|
|
133
|
+
*SCALAR_FIELD_GRADIENT.parameters[1:],
|
|
134
|
+
),
|
|
135
|
+
executor=_vector_gradient,
|
|
136
|
+
)
|