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.
Files changed (135) hide show
  1. {flowgraph-0.1.6/src/flowgraph.egg-info → flowgraph-0.1.7}/PKG-INFO +56 -3
  2. {flowgraph-0.1.6 → flowgraph-0.1.7}/README.md +55 -2
  3. {flowgraph-0.1.6 → flowgraph-0.1.7}/pyproject.toml +1 -1
  4. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/__init__.py +33 -10
  5. flowgraph-0.1.7/src/flowgraph/adapters/calculator.py +120 -0
  6. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/controls.py +6 -8
  7. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/cosapp.py +1 -1
  8. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/data_types.py +12 -1
  9. flowgraph-0.1.7/src/flowgraph/adapters/field_gradient.py +136 -0
  10. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/field_ops.py +74 -7
  11. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/files.py +3 -0
  12. flowgraph-0.1.7/src/flowgraph/adapters/filters.py +349 -0
  13. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/image_tools.py +135 -47
  14. flowgraph-0.1.7/src/flowgraph/adapters/mesh_analysis.py +282 -0
  15. flowgraph-0.1.7/src/flowgraph/adapters/mesh_composition.py +481 -0
  16. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/mesh_ops.py +38 -0
  17. flowgraph-0.1.7/src/flowgraph/adapters/paraview.py +107 -0
  18. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/plaid_bridge.py +23 -12
  19. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/readers.py +9 -7
  20. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/simple_sources.py +16 -12
  21. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/user_code.py +7 -1
  22. flowgraph-0.1.7/src/flowgraph/adapters/vector_tools.py +168 -0
  23. flowgraph-0.1.7/src/flowgraph/adapters/workflow.py +306 -0
  24. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/writers.py +4 -0
  25. flowgraph-0.1.7/src/flowgraph/adapters/zones.py +311 -0
  26. flowgraph-0.1.7/src/flowgraph/application/file_dependencies.py +136 -0
  27. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/port_conversions.py +91 -0
  28. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_cli.py +8 -6
  29. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_core.py +69 -6
  30. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_defaults.py +15 -9
  31. flowgraph-0.1.7/src/flowgraph/application/workflow_execution.py +358 -0
  32. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_io.py +51 -8
  33. flowgraph-0.1.7/src/flowgraph/application/workflow_session.py +436 -0
  34. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_web_catalog.py +5 -1
  35. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/core_cli.py +49 -1
  36. flowgraph-0.1.7/src/flowgraph/paraview_gateway.py +872 -0
  37. flowgraph-0.1.7/src/flowgraph/server/__init__.py +1 -0
  38. flowgraph-0.1.7/src/flowgraph/server/http.py +315 -0
  39. flowgraph-0.1.7/src/flowgraph/server/jobs.py +223 -0
  40. flowgraph-0.1.7/src/flowgraph/server/worker.py +36 -0
  41. flowgraph-0.1.7/src/flowgraph/server/workspace.py +136 -0
  42. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/AllFilters.json +2 -2
  43. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/version.py +1 -1
  44. {flowgraph-0.1.6 → flowgraph-0.1.7/src/flowgraph.egg-info}/PKG-INFO +56 -3
  45. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/SOURCES.txt +41 -3
  46. flowgraph-0.1.7/tests/test_array_nodes.py +226 -0
  47. flowgraph-0.1.7/tests/test_array_types.py +177 -0
  48. flowgraph-0.1.7/tests/test_cache_policy.py +191 -0
  49. flowgraph-0.1.7/tests/test_calculator.py +242 -0
  50. flowgraph-0.1.7/tests/test_conditional_workflow.py +243 -0
  51. flowgraph-0.1.7/tests/test_controls.py +123 -0
  52. flowgraph-0.1.7/tests/test_directory_dependencies.py +200 -0
  53. flowgraph-0.1.7/tests/test_extract_field.py +104 -0
  54. flowgraph-0.1.7/tests/test_extract_vector_value.py +109 -0
  55. flowgraph-0.1.7/tests/test_field_gradient.py +231 -0
  56. flowgraph-0.1.7/tests/test_fluency_optimization.py +119 -0
  57. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_image_tools.py +105 -2
  58. flowgraph-0.1.7/tests/test_local_server.py +536 -0
  59. flowgraph-0.1.7/tests/test_mesh_analysis.py +340 -0
  60. flowgraph-0.1.7/tests/test_mesh_composition.py +492 -0
  61. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_mesh_creation_tools.py +10 -0
  62. flowgraph-0.1.7/tests/test_node_selector.py +216 -0
  63. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_packaging.py +2 -2
  64. flowgraph-0.1.7/tests/test_paraview.py +1252 -0
  65. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_plaid_bridge.py +108 -6
  66. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_release.py +9 -1
  67. flowgraph-0.1.7/tests/test_remove_lonely_nodes.py +112 -0
  68. flowgraph-0.1.7/tests/test_scalars_to_array_1d.py +104 -0
  69. flowgraph-0.1.7/tests/test_transfer_fields.py +122 -0
  70. flowgraph-0.1.7/tests/test_user_code_mesh.py +38 -0
  71. flowgraph-0.1.7/tests/test_vector_field_gradient.py +184 -0
  72. flowgraph-0.1.7/tests/test_vector_tools.py +184 -0
  73. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_core.py +36 -0
  74. flowgraph-0.1.6/tests/test_wasm_execution.py → flowgraph-0.1.7/tests/test_workflow_execution.py +239 -11
  75. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_io.py +5 -3
  76. flowgraph-0.1.7/tests/test_workflow_session.py +634 -0
  77. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_web_catalog.py +67 -1
  78. flowgraph-0.1.7/tests/test_workspace_directories.py +54 -0
  79. flowgraph-0.1.7/tests/test_zones.py +214 -0
  80. flowgraph-0.1.6/src/flowgraph/adapters/filters.py +0 -182
  81. flowgraph-0.1.6/src/flowgraph/adapters/paraview.py +0 -42
  82. flowgraph-0.1.6/src/flowgraph/adapters/workflow.py +0 -189
  83. flowgraph-0.1.6/src/flowgraph/application/wasm_execution.py +0 -217
  84. flowgraph-0.1.6/tests/test_user_code_mesh.py +0 -18
  85. {flowgraph-0.1.6 → flowgraph-0.1.7}/LICENSE +0 -0
  86. {flowgraph-0.1.6 → flowgraph-0.1.7}/setup.cfg +0 -0
  87. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/__init__.py +0 -0
  88. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/__main__.py +0 -0
  89. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/documentation.py +0 -0
  90. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/file_conversion.py +0 -0
  91. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/imagej.py +0 -0
  92. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/mesh_creation_tools.py +0 -0
  93. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/mesh_generations.py +0 -0
  94. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/remote_files.py +0 -0
  95. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/sinks.py +0 -0
  96. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/table_tools.py +0 -0
  97. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/adapters/workflow_interfaces.py +0 -0
  98. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/__init__.py +0 -0
  99. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/node_registry.py +0 -0
  100. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_history.py +0 -0
  101. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflow_layout.py +0 -0
  102. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/application/workflows.py +0 -0
  103. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/domain/__init__.py +0 -0
  104. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/domain/filepath.py +0 -0
  105. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/domain/mesh_document.py +0 -0
  106. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/examples/AllFilters.py +0 -0
  107. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/examples/convert_format.py +0 -0
  108. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/resources.py +0 -0
  109. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/ColorCardboard.jpg +0 -0
  110. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/FlowGraph.h5 +0 -0
  111. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/FlowGraph.stl +0 -0
  112. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/FlowGraph.xdmf +0 -0
  113. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/SimpleReadMesh.json +0 -0
  114. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/SpherePoints.csv +0 -0
  115. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph/testdata/muscat-muscat-mesh-conversion.json +0 -0
  116. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/dependency_links.txt +0 -0
  117. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/entry_points.txt +0 -0
  118. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/requires.txt +0 -0
  119. {flowgraph-0.1.6 → flowgraph-0.1.7}/src/flowgraph.egg-info/top_level.txt +0 -0
  120. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_core_gui_boundary.py +0 -0
  121. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_cosapp.py +0 -0
  122. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_file_conversion.py +0 -0
  123. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_filepath.py +0 -0
  124. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_headless_boundary.py +0 -0
  125. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_imagej.py +0 -0
  126. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_mesh_ops.py +0 -0
  127. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_muscat_vtk.py +0 -0
  128. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_readers.py +0 -0
  129. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_remote_files.py +0 -0
  130. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_simple_sources.py +0 -0
  131. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_sinks.py +0 -0
  132. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_table_tools.py +0 -0
  133. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_cli.py +0 -0
  134. {flowgraph-0.1.6 → flowgraph-0.1.7}/tests/test_workflow_history.py +0 -0
  135. {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.6
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
- ## Development
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 and 3.13 on
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
- ## Development
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 and 3.13 on
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,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flowgraph"
7
- version = "0.1.6"
7
+ version = "0.1.7"
8
8
  description = "Headless typed workflow engine and execution runtime"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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("Scalars", (SET_STRING, SET_BOOLEAN, SET_INT, SET_FLOAT)),
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, SET_LIST_FLOAT, SET_VEC2D, SET_VEC2DI, SET_VEC3D),
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 Gens", (CSV_TO_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
- (CREATE_MUSCAT_ELEMENT_FILTER, FILTER_MESH_WITH_MUSCAT),
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
- if minimum >= maximum:
48
- raise ValueError("The slider minimum must be less than its maximum")
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", port=False),
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", port=False),
100
+ ParameterDefinition("value", ParameterKind.INTEGER, "Value", 50, "50"),
103
101
  ),
104
102
  )
105
103
 
@@ -169,7 +169,7 @@ COSAPP_WORKFLOW = NodeDefinition(
169
169
  "",
170
170
  "Path to a trusted Python file defining the CoSApp model",
171
171
  file_patterns=("*.py",),
172
- port=False,
172
+ port=True,
173
173
  ),
174
174
  ParameterDefinition(
175
175
  "factory",
@@ -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
+ )