"E2E: Setting num_blocks = 0 works for sequential models"
"E2E: Error handling for reserved names works"
"`compile`, `fit` and `optimizer` are protected names"

Expected `fit_obj <- parsnip::fit(spec, mpg ~ ., data = mtcars)` to run without any errors.
i Actually got a <simpleError> with text:
  ModuleNotFoundError: No module named 'keras'
  Run `reticulate::py_last_error()` for details.

E2E: Setting num_blocks = 0 works for sequential models

Expected `fit_obj <- parsnip::fit(wf, data = data)` to run without any errors.
i Actually got a <simpleError> with text:
  ModuleNotFoundError: No module named 'keras'
  Run `reticulate::py_last_error()` for details.

test_that("E2E: Functional spec (classification) works", {
# Define blocks for a simple forked functional model
input_block <- function(input_shape) keras3::layer_input(shape = input_shape)
# Add a default to `units` to work around a bug in the doc generator
# when handling args with no default. This doesn't affect runtime as the
path_block <- function(tensor, units = 16) {
tensor |> keras3::layer_dense(units = units, activation = "relu")
concat_block <- function(input_a, input_b) {
output_block_class <- function(tensor, num_classes) {
tensor |> keras3::layer_dense(units = num_classes, activation = "softmax")
# Create a spec with two parallel paths that are then concatenated
c(path_a = "input_a", path_b = "input_b")
expect_no_error(fit_obj <- parsnip::fit(wf, data = data))
preds_class <- predict(fit_obj, new_data = data[1:5, ], type = "class")
preds_prob <- predict(fit_obj, new_data = data[1:5, ], type = "prob")
expect_true(all(abs(rowSums(preds_prob) - 1) < 1e-5))
test_that("E2E: Functional spec tuning (including repetition) works", {
# value is always overridden by the tuning grid.
dense_block <- function(tensor, units = 16) {
# This block has a single input, so it can be repeated
grid <- grid_regular(params, levels = 2)
control <- control_grid(save_pred = FALSE, verbose = FALSE)
testthat::skip(paste("Tuning failed with error:", as.character(tune_res)))
test_that("E2E: Multi-input, single-output functional classification works", {
on.exit(options(kerasnip.show_removal_messages = TRUE), add = TRUE)
input_block_1 <- function(input_shape) layer_input(shape = input_shape, name = "input_1")
input_block_2 <- function(input_shape) layer_input(shape = input_shape, name = "input_2")
dense_path <- function(tensor, units = 16) {
tensor |> layer_dense(units = units, activation = "relu")
concat_block <- function(in_1, in_2) layer_concatenate(list(in_1, in_2))
layer_dense(tensor, units = num_classes, activation = "softmax")
concatenated = inp_spec(concat_block, c(path_a = "in_1", path_b = "in_2")),
spec <- multi_in_class(fit_epochs = 2) |> set_engine("keras")
y <- factor(sample(c("a", "b"), 100, replace = TRUE))
input_a = lapply(seq_len(nrow(x1)), function(i) x1[i, , drop = FALSE]),
input_b = lapply(seq_len(nrow(x2)), function(i) x2[i, , drop = FALSE]),
rec <- recipe(outcome ~ input_a + input_b, data = train_df)
expect_no_error(fit_obj <- parsnip::fit(wf, data = train_df))
input_a = lapply(seq_len(5), function(i) matrix(rnorm(5), ncol = 5)),
input_b = lapply(seq_len(5), function(i) matrix(rnorm(3), ncol = 3))
preds <- predict(fit_obj, new_data = new_data_df)
test_that("E2E: Functional spec with pre-constructed optimizer works", {
my_optimizer <- keras3::optimizer_sgd(learning_rate = 0.001)
test_that("E2E: Functional spec with string loss works", {