Skip to content

release: v7.17.0 — neural network metamodel expansion and a Spring Boot generator - #608

Merged
ArmenSl merged 250 commits into
masterfrom
development
Sep 22, 2026
Merged

ArmenSl merged 250 commits into
masterfrom
development

Conversation

@ArmenSl

@ArmenSl ArmenSl commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Minor release: a substantially expanded neural network metamodel, and a new Spring Boot generator. The neural network work was contributed by @DaoudiNadia (#584); the Spring Boot generator by @miloss01 (#607), which started life as his master's thesis. Both were reviewed and completed by the BESSER team.

  • 19 new tensor operations join the six the metamodel understood, covering the plumbing real architectures need between layers — reducing over an axis, resizing a feature map, slicing, combining branches — which previously had to be hand-written into the generated file.
  • Layers accept the parameters their framework equivalents have: dilation/groups on convolutions, padding_idx on embeddings, eps/momentum/affine/track_running_stats on normalization, dimension on dropout, bias across conv/linear/recurrent.
  • NN.input_var / NN.return_vars make non-sequential architectures — branches, skip connections, encoder→decoder hidden state — expressible in the model rather than patched into the output.
  • A Spring Boot backend generator: a domain model becomes a Maven project with JPA entities, Spring Data repositories, a service layer, REST controllers and .http files.

Test plan

  • Full suite on merged development: 1605 passed, 9 skipped, 3 xfailed
  • CI green on both source PRs (lint, CodeQL, tests 3.11 + 3.12)
  • Docs build: no new warnings; v7.17.0 and generators/spring pages render
  • Spring generator invoked exactly as _generate_standard does — 13 files, mvnw at project root, pom.xml present
  • Generated Spring project compiles: ./mvnw -B -q compile exits 0
  • Frontend on merged develop: 365 tests pass, npm ci clean, build:webapp green, i18n check passes
  • Post-merge: tag v7.17.0, publish release, deploy backend + frontend

Note: the new non-English editor strings are machine-authored and are flagged for native-speaker review in a follow-up.

miloss01 and others added 30 commits March 7, 2026 22:39
Fixed line 112 in setup_nn_components.py.j2 where conv layer
was using input variable instead of padded output variable.
Changed from module_details[2] to module_details[1] for the
second line in ZeroPadding block.
…erator

- Add setup_standalone_activation() to handle activations from functional API (F.relu)
  that appear after tensorops as GeneralLayer instances
- Add add_separate_activation_if_needed() for layers that don't support activation
  parameter (BatchNorm, LayerNorm, Dropout, Embedding, Pooling, Flatten)
- Implement shared activation instances: one definition per activation type,
  reused across all calls (e.g., 6 relu calls use 1 shared activation_relu layer)
- Use DEF: prefix for shared activation definitions (printed in __init__ only)
- Use CALL: prefix for activation call references (printed in forward method)
- Update template to handle _activ suffix entries with DEF:/CALL: prefixes
- Update get_input_var() to support _activ entries in name_module_input lookups
- Update get_layer_syntax() to handle standalone activation case (cls_name == GeneralLayer)
- Skip adding _layer entry when layer_synt is None (standalone activations)
TensorFlow generator (utils_tf.py):
- Add add_permute method to create tf.transpose operations for CNN layers
- Read permute_in/permute_out flags in setup_conv and setup_pooling

PyTorch generator (utils_pytorch.py):
- Fix BatchNorm syntax typo: .BatchNorm(){dim}d -> .BatchNorm{dim}d

Shared code (utils_nn.py):
- Fix channel_last condition to handle all three cases: None (TF always adds permutes), True (PyTorch adds permutes), False (PyTorch skips permutes)
- Add hasattr check before calling add_permute since only TF generator has this method, PyTorch generator does not
Metamodel changes (besser/BUML/metamodel/nn/neural_network.py):
- Add reduce_dim parameter to TensorOp for dimension-reducing operations
- Add actual_vars parameter to TensorOp to distinguish between RNN output
  and hidden state components
- Add new tensor operation types: mean, squeeze, unsqueeze, and binary
  operations (add, subtract, multiply, divide)
- Add both as valid RNN return_type to return both output sequences
  and hidden states

Core generator utilities (besser/generators/nn/utils_nn.py):
- Extend get_layers_output_for_tensorops() to accept actual_vars parameter
- Use actual_vars to select correct component when RNN has return_type=both
  (either hidden variable or last timestep of output sequence)
- Store hidden variable as 5th element in modules_details for return_type=both
- Pass actual_vars through from TensorOp to parameter extraction functions

PyTorch generator (besser/generators/nn/pytorch/):
- Add template handling for return_type=both to unpack both output and
  hidden states from RNN calls
- Handle bidirectional case by concatenating forward and backward hidden states
- Add code generation for mean, squeeze, unsqueeze, and binary operations

TensorFlow generator (besser/generators/nn/tf/):
- Add return_sequences=True, return_state=True flags for return_type=both
- Add template handling to unpack both components from RNN/LSTM calls
- Handle bidirectional unpacking with separate forward and backward states
- Add code generation for mean, squeeze, unsqueeze, and binary operations

These changes enable migration of complex RNN patterns where both output
sequences and hidden states are used simultaneously in the forward pass.
The generator now accepts numeric values (int, float) directly in
layers_of_tensors instead of only layer names, supporting operations
like multiply by constant.
When tensor operations reference other tensor operations (e.g., nested binary
operations like x + y + z), the generator now assigns unique variable names to
intermediate results. This prevents variable name collisions where multiple
operands would incorrectly resolve to the same variable.

The fix uses a two-pass approach: first identify which tensorops are referenced
by others, then assign unique names (_op_N) to those that need them.
Standalone activations inside Sequential blocks now use direct layer definitions
instead of the shared activation pattern. Added is_subnn parameter to track when
layers are being processed within a Sequential and adjusted the syntax generation
accordingly.
Update get_layers_output_for_tensorops to check for _activ suffix when looking up layer outputs in tensor operations like concatenate. This enables proper handling of standalone GeneralLayer activation instances that are stored with _activ suffix instead of _layer suffix in modules_details.

Fixes KeyError when concat operations reference activation layers created from nested functional calls.
Add 'subscript' as valid tns_type in TensorOp metamodel to support general slicing and subscripting operations beyond RNN specific slicing. Add subscript_indices attribute to store slice pattern as string representation.

Update TensorOp initialization to set subscript_indices before tns_type to allow validation. Add validation requiring subscript_indices and non-empty layers_of_tensors for subscript type.

Update get_tensorop_params in utils_nn.py to handle subscript operations by extracting input variable from layers_of_tensors. Update TensorFlow and PyTorch generators to render subscript TensorOps as inline subscript syntax using subscript_indices pattern.

Enables representation of subscripting patterns like tensor[:, :, -1] or embeddings[:, 0] in neural network models while maintaining correct variable tracking through TensorOp instances.
… output

- Add INPUT marker handling in get_input_var to return network input variable
- Extend get_tensorop_params to check name_module_input for TensorOps and use 'inp'
  variable when INPUT marker is set
- Update subscript handling to check name_module_input before layers_of_tensors
- Store TensorOp object in modules_details (3 elements instead of 2) to enable
  template access to name_module_input attribute
- Add INPUT preservation detection in print_forward templates (TF and PyTorch) to
  generate 'inp = x' when modules reference network input
- Fix activation function output in TF template by adding whitespace control to
  properly render DEF: activations in __init__
- Add guard to only access module_details[3] for _layer and _activ modules, not
  _nn modules which use dict structure
Add 'max' to valid TensorOp types in neural_network.py metamodel.

Add tf.reduce_max syntax generation in utils_tf.py for max TensorOp type,
matching mean operation pattern with axis parameter from reduce_dim.

Add source layer resolution in get_tensorop_params for mean and max operations.
When layers_of_tensors is set, resolve the source layer name to get the correct
output variable, checking both _layer and _op module name patterns. This ensures
mean and max operate on the specified input tensor rather than the previous
output in sequence
Add check for Sequential sub-networks in get_layers_output_for_tensorops.
Sub-networks are stored with format name_N_nn (with counter), not just
name_nn. Use pattern matching to find the correct key.
- Convert LayerNorm dimension size to axis indices using range(-num_axes, 0)
- Handle 'INPUT' source layer in mean/max TensorOp to use 'inp' variable
Add shape_dim type for extracting tensor dimensions (e.g., b = tf.shape(x)[0]).
Fix variable naming to prevent shape variables from being overwritten by
subsequent operations.

- Add 'shape_dim' to valid TensorOp types in metamodel
- Generate tf.shape()[index] syntax for shape_dim ops
- Track reshape source variables via layers_of_tensors
- Fix get_out_var_input_reused to handle non-standard variable names robustly
- Prevent reusing shape variable names (b, t) for subsequent x_N operations
Add check to assign _op_ prefix to all binary operations,
preventing them from overwriting layer output variables.
Add dimension parameter to DropoutLayer for 1D/2D/3D spatial variants.
When dimension is None, generates regular Dropout. When set, generates
SpatialDropout (TF) or Dropout{1,2,3}d (PyTorch).

- Add dimension parameter to DropoutLayer.__init__
- Update TF generator to use SpatialDropout{1,2,3}D
- Update PyTorch generator to use Dropout{1,2,3}d
- Fix transpose(dim0, dim1) conversion to TensorFlow full permutation (e.g., transpose(1, 2) → perm=[0, 2, 1])
- Add transpose source variable resolution via layers_of_tensors in utils_nn.py
- Assumes 3D tensors for 2-arg transpose (most common case for RNN/CNN-1D)
DaoudiNadia and others added 29 commits September 10, 2026 17:01
- Add explanation to tns_type validation error message that transpose_dim
  is capped at 2 by design; for arbitrary N-dimensional tns_type='permute'
  should be used instead
- Document in TensorOp docstring the distinction between transpose
  (exactly 2 dims, common 3D case) and permute (full arbitrary
  permutation over any number of dimensions)
… object support

- Clarify in TensorOp class docstring and layers_of_tensors getter/setter
  that elements must be layer name strings or scalar values (float or int)
- Explicitly note that passing Layer objects directly is no longer
  supported; the layer name string should be used
… validation before generation

- Add resolve_var_chain() to NN to propagate input_var and return_vars
  into the first and last modules
- Remove mutation from _validate_input_output_var_chain() so validate()
  is now side-effect free
- Call resolve_var_chain() then validate() in NNCodeGenerator.__init__
  before building modules_details, so generation always starts from a
  clean and valid model
- Remove left-over validate() from tensorop class
- Rename skip_validation flag to allow_unresolved_shapes to better
  reflect its purpose in TF to PyTorch migration
Remove tests/generators/nn_migration/ from version control; the
fixtures have no test module reading them and are out of date with
the current generators.
Make input_var take priority over layers_of_tensors when resolving the
input variable for a TensorOp, consistently across all operation types
in both generators.
…date docstrings

- Add TensorOp Parameters section covering all operation types, their
  required and optional parameters, and the shared attributes with
  input resolution order
- Add Layer Parameters section covering BESSER-specific attributes
  shared across all layers and RNN-specific attributes
- Update the Numerical bounds validation bullet to reflect the actual
  checks in place
- Expand the subscript_indices docstring with the full dict structure
  and an example
- Remove the misleading Required note from split_sizes docstring
…e, subscript, and identity tensorops from the editor
…tion

Skip input_var/output_var mismatch checks when module values are None,
preventing false validation errors
…hape_dim block

Factor the repeated base-layer fields (is_layer_call, input_var, output_var) emission into a single _append_base_layer_fields() helper called once per layer branch.

Remove dead unreachable elif block for shape_dim TensorOp (already handled earlier in the reduce operations branch).
…ation

When previous module is multi-output (like RNN family returning (output, hidden_state)), the downstream layer must specify which output to consume. The validation was checking only input_var but name_module_input is also valid.

Also fix three safety test exception types to match implementation (TypeError not ValueError).

Also update cnn_rnn.json and lstm.json fixtures to satisfy validation requirements.
# Conflicts:
#	besser/utilities/web_modeling_editor/backend/backend.py
The new layer and tensor-op parameters were added with a bare `*` in every
constructor, which silently turned pre-existing parameters keyword-only:
TensorOp("t", "reshape", None, None, [1, 2]) worked on 7.16.1 and raised
TypeError here. The marker now sits after the last parameter that already
existed on development, so only genuinely new parameters are keyword-only
and no model written against an earlier release breaks.

Three defects in validate() are fixed alongside it. A mis-indented block
nested the pad, dropout, split, permute and variable-name checks under
`if tns.interpolate_mode is not None:`; since the setter accepts None, that
path silently skipped roughly 130 lines of validation. The subscript_indices
branch appended "must be dict" and then subscripted the element anyway,
raising KeyError out of a method contracted to collect errors. And eval()
was used to parse user-authored dimension strings, with an except clause
that missed NameError.

Also drops the duplicated CNN.permute_in/permute_out properties that shadowed
the base Layer ones, exports ALLOWED_TENSOR_OP_TYPES so the converter stops
carrying its own copy of the list, and fixes two docstrings that promised
validation the setters do not perform.
Whether an attribute had been set explicitly was recorded in a sidecar that
only the JSON processor populated, so models built in Python or imported from
BUML code lost most of their configuration on the way back out. A Conv2D
constructed with bias, permute_in, permute_out, is_layer_call, dilation,
groups and input_reused came back as Conv2D(name, kernel_dim, out_channels,
stride_dim) -- seven of nine values gone. The check now falls back to
comparing the value against the default declared by the constructor, so both
exporters agree regardless of how the model was built.

Four fields were lost in narrower ways. TensorOp.shape_dim was parsed and then
never passed to the constructor or emitted by either exporter. repeat_dim went
through parse_list_of_ints even though the metamodel declares list[int | str],
so any entry naming a layer discarded the whole list. subscript_indices was
emitted as a Python repr, which is not valid JSON and so failed to parse on
import. The code builder omitted dilation and groups for convolutions and
permute_in/permute_out for embedding, dropout and batch-norm layers.

Malformed subscript_indices and pad_amount payloads were also being swallowed
into None; they now raise a ValueError naming the element and field, matching
how the neighbouring fields already report bad input. The AST-safety
rejections in the BUML reader raise ValueError rather than TypeError so they
surface as HTTP 400 instead of a generic 500.

Adds 15 regression tests, including the Conv2D case above.
The v7.17.0 notes file was present but missing from the releases toctree, so
Sphinx treated it as orphaned and it did not appear in the built docs. It is
now wired in at the top of the list, and setup.cfg is bumped to match.

Deleting the generated tests/BUML/metamodel/nn/output tree also broke the
build: docs/source/generators/pytorch.rst and tensorflow.rst literalinclude
the tutorial_example output, and Sphinx failed to find it. The two files that
the docs reference are restored -- regenerating them reproduces what the
branch had committed before the directory was removed, so the deletion was
housekeeping that overlooked the references.

The neural network page described the new layer parameters as "standard
parameters expected from the equivalent layer in a deep learning framework"
without naming any of them; bias, dilation, groups, padding_idx, eps,
momentum, affine, track_running_stats and dimension are now documented with
their types, defaults and effects. Two docstrings in neural_network.py used
inline literals spanning a line break, which docutils cannot parse; they are
now a literal block and a single-line literal respectively.

The notes cover both features in this release and credit the contributors.
… that compiles

The generator could not be instantiated by the web editor at all:
SpringBackendGenerator required spring_boot_version, java_version and app_name
positionally before output_dir, while the router calls every generator as
generator_class(model, output_dir=...). Those are now keyword arguments with
defaults, matching the rest of besser/generators.

The generated project also did not compile. Four copies of the B-UML to Java
type map had drifted apart: TimeType mapped to LocalTime in the entity and
controller generators but to LocalDateTime in the repository and service
generators, so an entity field and the methods taking it disagreed on type.
The map now lives once in java_types.py, and the findAllBy signature builder
is shared so the two sides cannot drift again.

Association ownership compared a Multiplicity object against the integer 1,
which is always unequal, so every association resolved to the same end as
owner and mappedBy was placed arbitrarily. Ownership is now decided from
multiplicity.max against UNLIMITED_MAX_MULTIPLICITY, which also fixes
self-associations emitting one end twice instead of both.

B-UML names reached the filesystem unsanitized. NamedElement.name rejects
spaces and hyphens but permits "..", "/" and "\", so a class named ../../evil
was a valid model that wrote outside output_dir. Class, field, package and
file names now go through Java identifier sanitization, and the new
_generate_spring router branch resolves the project directory with the
existing _safe_path helper rather than a raw os.path.join.

The Maven wrapper shipped as three .j2 templates containing no Jinja tags at
all, rendered into .mvn/ where Maven does not look for them. They are static
files under resources/ now, copied to the project root with the executable bit
and fixed line endings, and declared as package data so they ship in the wheel.

Also: the generator is registered in SUPPORTED_GENERATORS and dispatched with
its configuration rather than falling through to the generic path that ignored
it; unmapped types fall back to Object instead of raising KeyError; a class
with no identifier reports which class rather than a bare StopIteration; the
controller uses the resolved identifier accessor instead of a hardcoded
setId; enumeration literals and associations are sorted so output is
deterministic; and example.py, a 206-line scratch script no other generator
ships, is removed.

Adds tests/generators/spring with 24 tests covering structure, content,
determinism, name sanitization and config pass-through. Verified end to end by
running mvnw compile on a generated project.
The traversal test asserted evil.java while the generator correctly writes
Evil.java, since a class name is capitalized on the way to a Java identifier.
Windows matched it case-insensitively so the assertion passed locally and
failed on the Linux CI runners.
The branch pinned b14dadec, which predates the NN diagram work it depends on
by more than ten commits. Updated to 8faa300a, the develop head after WME #181
and #194 merged, so the backend and editor ship the same feature set.
feat: enhance neural network support with extended metamodel, generators and web editor
…erator

Feature/spring boot project generator
@ArmenSl
ArmenSl merged commit 98ad828 into master Sep 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants