// main.go
package main

import (
	"Learn/utils"
	"bufio"
	"encoding/gob"
	"fmt"
	"gorgonia.org/gorgonia"
	tensor "gorgonia.org/tensor"
	"log"
	"os"
	"strconv"
	"strings"
)

const (
	TrainMode         = "t"
	ValidationMode    = "v"
	DialogueMode      = "d"
	ModelFile         = "models/model.gob"
	VocabFile         = "models/vocab.gob"
	TrainingDataDir   = "train"
	ValidationDataDir = "valid"
	EmbeddingSize     = 128 // Размер эмбеддинга
	HiddenSize        = 256 // Размер скрытого слоя
	MaxSeqLen         = 1
)

// Model представляет параметры модели
type Model struct {
	Embedding []float32
	Hidden    []float32
	Output    []float32
	bHidden   []float32
	bOutput   []float32
}

func main() {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Введите режим работы (t для обучения, v для валидации, d для диалога): ")
	mode, _ := reader.ReadString('\n')
	mode = strings.TrimSpace(mode)

	switch mode {
	case TrainMode:
		train(reader)
	case ValidationMode:
		validate()
	case DialogueMode:
		dialogue(reader)
	default:
		fmt.Println("Некорректный режим работы.")
	}
}

// must обрабатывает ошибку или возвращает узел
func must(n *gorgonia.Node, err error) *gorgonia.Node {
	if err != nil {
		log.Fatalf("Ошибка: %v", err)
	}
	return n
}

// flatten преобразует двумерный срез в одномерный
func flatten(data [][]float32) []float32 {
	var flat []float32
	for _, row := range data {
		flat = append(flat, row...)
	}
	return flat
}

// PreprocessData преобразует сырые данные в пары (input, target)
func PreprocessData(data []string, vocab utils.Vocabulary) ([][]float32, [][]float32) {
	var input [][]float32
	var target [][]float32

	sampleCount := 0
	for _, line := range data {
		tokens := utils.Tokenize(line)
		for j := 0; j < len(tokens)-1; j++ {
			currentToken := tokens[j]
			nextToken := tokens[j+1]

			inputID, exists := vocab.TokenToID[currentToken]
			if !exists {
				inputID = vocab.TokenToID["<UNK>"]
			}

			targetID, exists := vocab.TokenToID[nextToken]
			if !exists {
				targetID = vocab.TokenToID["<UNK>"]
			}

			inputSeq := make([]float32, vocabSize)
			inputSeq[inputID] = 1.0 // One-hot encoding

			targetSeq := make([]float32, vocabSize)
			targetSeq[targetID] = 1.0 // One-hot encoding

			input = append(input, inputSeq)
			target = append(target, targetSeq)
			sampleCount++
		}
	}
	fmt.Printf("Предобработано %d пар (input, target)\n", sampleCount)
	return input, target
}

// readInt читает строку из reader и преобразует её в int
func readInt(reader *bufio.Reader, prompt string) int {
	for {
		fmt.Print(prompt)
		input, err := reader.ReadString('\n')
		if err != nil {
			log.Fatalf("Ошибка при чтении ввода: %v", err)
		}
		input = strings.TrimSpace(input)
		val, err := strconv.Atoi(input)
		if err != nil {
			fmt.Println("Неверный ввод. Пожалуйста, введите целое число.")
			continue
		}
		return val
	}
}

// readString читает строку из reader после вывода prompt
func readString(reader *bufio.Reader, prompt string) string {
	fmt.Print(prompt)
	input, err := reader.ReadString('\n')
	if err != nil {
		log.Fatalf("Ошибка при чтении ввода: %v", err)
	}
	return strings.TrimSpace(input)
}

// train осуществляет обучение модели
func train(reader *bufio.Reader) {
	// Ввод параметров обучения
	epochs := readInt(reader, "Введите количество эпох: ")
	batchSize := readInt(reader, "Введите размер батча: ")
	maxTrainMessages := readInt(reader, "Введите максимальное количество сообщений для обучения (0 для всех): ")
	optimizerChoice := readString(reader, "Выберите оптимизатор (adam/sgd): ")

	// Загрузка данных
	fmt.Println("Загрузка данных для обучения...")
	data := utils.LoadData(TrainingDataDir, maxTrainMessages)
	totalLoaded := len(data)
	if maxTrainMessages > 0 && totalLoaded > maxTrainMessages {
		data = data[:maxTrainMessages]
		fmt.Printf("Используется %d из %d доступных сообщений.\n", maxTrainMessages, totalLoaded)
	} else {
		fmt.Printf("Используется все %d доступных сообщений.\n", totalLoaded)
	}

	// Построение словаря
	fmt.Println("Построение словаря...")
	vocab := utils.BuildVocabulary(data, 2) // Минимальная частота = 2
	utils.SaveVocabulary(vocab, VocabFile)
	vocabSize := len(vocab.TokenToID)
	fmt.Printf("Размер словаря: %d\n", vocabSize)

	// Предобработка данных
	fmt.Println("Предобработка данных...")
	input, target := PreprocessData(data, vocab)
	if len(input) == 0 || len(target) == 0 {
		log.Fatalf("Нет данных для обучения после предобработки.")
	}

	// Инициализация графа Gorgonia
	g := gorgonia.NewGraph()

	// Определение узлов ввода и цели
	x := gorgonia.NewMatrix(g,
		tensor.Float32, // Изменено на Float32
		gorgonia.WithShape(batchSize, vocabSize),
		gorgonia.WithName("x"))

	y := gorgonia.NewMatrix(g,
		tensor.Float32, // Изменено на Float32
		gorgonia.WithShape(batchSize, vocabSize),
		gorgonia.WithName("y"))

	// Эмбеддинговый слой
	embedding := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize, EmbeddingSize),
		gorgonia.WithName("Embedding"),
		gorgonia.WithInit(gorgonia.GlorotN(1.0)))

	// Скрытый слой
	hidden := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(EmbeddingSize, HiddenSize),
		gorgonia.WithName("Hidden"),
		gorgonia.WithInit(gorgonia.GlorotN(1.0)))

	// Выходной слой
	output := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(HiddenSize, vocabSize),
		gorgonia.WithName("Output"),
		gorgonia.WithInit(gorgonia.GlorotN(1.0)))

	// Параметры смещения
	bHidden := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(1, HiddenSize),
		gorgonia.WithName("bHidden"),
		gorgonia.WithInit(gorgonia.Zeroes()))

	bOutput := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(1, vocabSize),
		gorgonia.WithName("bOutput"),
		gorgonia.WithInit(gorgonia.Zeroes()))

	// Векторизация входных данных через эмбеддинг
	embedded := must(gorgonia.Mul(x, embedding))
	embedded = must(gorgonia.Add(embedded, bHidden))

	// Прогноз через скрытый слой
	hiddenPred := must(gorgonia.Mul(embedded, hidden))
	hiddenPred = must(gorgonia.Add(hiddenPred, bOutput))

	// Функция потерь: среднеквадратичная ошибка
	errorLayer := must(gorgonia.Sub(hiddenPred, y))
	squared := must(gorgonia.Square(errorLayer))
	sumSquared, errCreateSum := gorgonia.Sum(squared)
	if errCreateSum != nil {
		log.Fatalf("Ошибка создания узла Sum: %v", errCreateSum)
	}
	elements := float32(batchSize * vocabSize)
	loss := must(gorgonia.Div(sumSquared, gorgonia.NewConstant(elements)))

	// Создание машины ленточных вычислений (Tape Machine)
	machine := gorgonia.NewTapeMachine(g, gorgonia.BindDualValues(embedding, hidden, output, bHidden, bOutput))

	// Определение оптимизатора
	var solver gorgonia.Solver
	switch strings.ToLower(optimizerChoice) {
	case "adam":
		solver = gorgonia.NewAdamSolver(gorgonia.WithLearnRate(0.001))
	case "sgd":
		solver = gorgonia.NewVanillaSolver(gorgonia.WithLearnRate(0.01))
	default:
		log.Fatalf("Неизвестный оптимизатор: %s", optimizerChoice)
	}

	// Цикл обучения
	fmt.Println("Начало обучения...")
	for epoch := 1; epoch <= epochs; epoch++ {
		totalLoss := float32(0)
		numBatches := 0
		for i := 0; i < len(input); i += batchSize {
			end := i + batchSize
			if end > len(input) {
				end = len(input)
			}
			currentBatchSize := end - i

			batchInput := input[i:end]
			batchTarget := target[i:end]

			// Если последний батч меньше batchSize, заполнить его нулями
			if currentBatchSize < batchSize {
				padding := batchSize - currentBatchSize
				for j := 0; j < padding; j++ {
					batchInput = append(batchInput, make([]float32, vocabSize))
					batchTarget = append(batchTarget, make([]float32, vocabSize))
				}
			}

			// Создание тензоров для текущего батча
			xVal := tensor.New(tensor.WithShape(batchSize, vocabSize), tensor.Float32, tensor.WithBacking(flattenFloat32(batchInput)))
			yVal := tensor.New(tensor.WithShape(batchSize, vocabSize), tensor.Float32, tensor.WithBacking(flattenFloat32(batchTarget)))

			// Ввод значений в граф
			if err := gorgonia.Let(x, xVal); err != nil {
				log.Fatalf("Ошибка установки x: %v", err)
			}
			if err := gorgonia.Let(y, yVal); err != nil {
				log.Fatalf("Ошибка установки y: %v", err)
			}

			// Выполнение форвардного и бэкового прохода
			if err := machine.RunAll(); err != nil {
				log.Fatalf("Ошибка выполнения графа: %v", err)
			}

			// Получение значения потерь
			lossVal, ok := loss.Value().Data().([]float32)
			if !ok {
				log.Fatalf("Не удалось преобразовать значение потерь")
			}
			currentLoss := lossVal[0]
			totalLoss += currentLoss
			numBatches++

			// Обновление параметров
			if err := solver.Step(gorgonia.NodesToValueGrads([]*gorgonia.Node{embedding, hidden, output, bHidden, bOutput})); err != nil {
				log.Fatalf("Ошибка обновления параметров: %v", err)
			}

			// Сброс машины для следующего шага
			machine.Reset()

			// Прогресс
			epochProgress := float32(end) / float32(len(input)) * 100
			if epochProgress > 100 {
				epochProgress = 100
			}
			overallProgress := float32(epoch) / float32(epochs) * 100
			if overallProgress > 100 {
				overallProgress = 100
			}
			fmt.Printf("Epoch %d: Processed batch from %d to %d (%.2f%% complete) (%.2f%% all complete)\n",
				epoch, i, end, epochProgress, overallProgress)
		}
		avgLoss := totalLoss / float32(numBatches)
		fmt.Printf("Epoch %d завершен. Средняя потеря: %.4f\n", epoch, avgLoss)
	}

	// Сохранение модели
	fmt.Println("Сохранение модели...")
	// Извлечение значений параметров
	embeddingData := extractValuesFloat32(embedding)
	hiddenData := extractValuesFloat32(hidden)
	outputData := extractValuesFloat32(output)
	bHiddenData := extractValuesFloat32(bHidden)
	bOutputData := extractValuesFloat32(bOutput)

	model := Model{
		Embedding: embeddingData,
		Hidden:    hiddenData,
		Output:    outputData,
		bHidden:   bHiddenData,
		bOutput:   bOutputData,
	}
	saveModel(&model)
	fmt.Println("Обучение завершено и модель сохранена.")
}

// flattenFloat32 преобразует двумерный срез float32 в одномерный
func flattenFloat32(data [][]float32) []float32 {
	var flat []float32
	for _, row := range data {
		flat = append(flat, row...)
	}
	return flat
}

// extractValuesFloat32 извлекает значения из узла как []float32
func extractValuesFloat32(node *gorgonia.Node) []float32 {
	value := node.Value()
	if value == nil {
		log.Fatalf("Узел %s не имеет значения", node.Name())
	}
	data, ok := value.Data().([]float32)
	if !ok {
		log.Fatalf("Невозможно преобразовать данные узла %s", node.Name())
	}
	return data
}

// saveModel сохраняет модель в файл
func saveModel(m *Model) {
	// Проверка и создание директории models, если она не существует
	if _, err := os.Stat("models"); os.IsNotExist(err) {
		err := os.MkdirAll("models", os.ModePerm)
		if err != nil {
			log.Fatalf("Не удалось создать директорию models: %v", err)
		}
	}

	file, err := os.Create(ModelFile)
	if err != nil {
		log.Fatalf("Не удалось создать файл модели: %v", err)
	}
	defer file.Close()

	encoder := gob.NewEncoder(file)
	err = encoder.Encode(m)
	if err != nil {
		log.Fatalf("Не удалось закодировать модель: %v", err)
	}
}

// loadModel загружает модель из файла
func loadModel() *Model {
	file, err := os.Open(ModelFile)
	if err != nil {
		log.Fatalf("Не удалось открыть файл модели: %v", err)
	}
	defer file.Close()

	var m Model
	decoder := gob.NewDecoder(file)
	err = decoder.Decode(&m)
	if err != nil {
		log.Fatalf("Не удалось декодировать модель: %v", err)
	}
	return &m
}

// validate выполняет валидацию модели
func validate() {
	// Загрузка модели
	model := loadModel()

	// Загрузка словаря
	vocab := utils.LoadVocabulary(VocabFile)
	vocabSize := len(vocab.TokenToID)
	fmt.Printf("Размер словаря: %d\n", vocabSize)

	// Загрузка валидационных данных
	fmt.Println("Загрузка данных для валидации...")
	data := utils.LoadData(ValidationDataDir, 10000) // Измените лимит при необходимости
	fmt.Printf("Загружено %d валидационных записей\n", len(data))

	// Предобработка данных
	input, target := PreprocessData(data, vocab)
	if len(input) == 0 || len(target) == 0 {
		log.Fatalf("Нет данных для валидации после предобработки.")
	}

	// Инициализация графа
	g := gorgonia.NewGraph()

	// Определение узлов ввода и цели
	x := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(len(input), vocabSize),
		gorgonia.WithName("x"))

	y := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(len(target), vocabSize),
		gorgonia.WithName("y"))

	// Эмбеддинговый слой
	embedding := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize, EmbeddingSize),
		gorgonia.WithName("Embedding"))

	// Скрытый слой
	hidden := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(EmbeddingSize, HiddenSize),
		gorgonia.WithName("Hidden"))

	// Выходной слой
	output := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(HiddenSize, vocabSize),
		gorgonia.WithName("Output"))

	// Параметры смещения
	bHidden := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(1, HiddenSize),
		gorgonia.WithName("bHidden"))

	bOutput := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(1, vocabSize),
		gorgonia.WithName("bOutput"))

	// Присвоение загруженных значений
	embeddingVal := tensor.New(tensor.WithShape(vocabSize, EmbeddingSize), tensor.Float32, tensor.WithBacking(model.Embedding))
	hiddenVal := tensor.New(tensor.WithShape(EmbeddingSize, HiddenSize), tensor.Float32, tensor.WithBacking(model.Hidden))
	outputVal := tensor.New(tensor.WithShape(HiddenSize, vocabSize), tensor.Float32, tensor.WithBacking(model.Output))
	bHiddenVal := tensor.New(tensor.WithShape(1, HiddenSize), tensor.Float32, tensor.WithBacking(model.bHidden))
	bOutputVal := tensor.New(tensor.WithShape(1, vocabSize), tensor.Float32, tensor.WithBacking(model.bOutput))

	if err := gorgonia.Let(embedding, embeddingVal); err != nil {
		log.Fatalf("Ошибка при установке Embedding: %v", err)
	}
	if err := gorgonia.Let(hidden, hiddenVal); err != nil {
		log.Fatalf("Ошибка при установке Hidden: %v", err)
	}
	if err := gorgonia.Let(output, outputVal); err != nil {
		log.Fatalf("Ошибка при установке Output: %v", err)
	}
	if err := gorgonia.Let(bHidden, bHiddenVal); err != nil {
		log.Fatalf("Ошибка при установке bHidden: %v", err)
	}
	if err := gorgonia.Let(bOutput, bOutputVal); err != nil {
		log.Fatalf("Ошибка при установке bOutput: %v", err)
	}

	// Векторизация входных данных через эмбеддинг
	embedded := must(gorgonia.Mul(x, embedding))
	embedded = must(gorgonia.Add(embedded, bHidden))

	// Прогноз через скрытый слой
	hiddenPred := must(gorgonia.Mul(embedded, hidden))
	hiddenPred = must(gorgonia.Add(hiddenPred, bOutput))

	// Функция потерь: среднеквадратичная ошибка
	errorLayer := must(gorgonia.Sub(hiddenPred, y))
	squared := must(gorgonia.Square(errorLayer))
	sumSquared, errCreateSum := gorgonia.Sum(squared)
	if errCreateSum != nil {
		log.Fatalf("Ошибка создания узла Sum: %v", errCreateSum)
	}
	elements := float32(len(input) * vocabSize)
	loss := must(gorgonia.Div(sumSquared, gorgonia.NewConstant(elements)))

	// Создание машины ленточных вычислений (Tape Machine)
	machine := gorgonia.NewTapeMachine(g)

	// Создание тензоров для валидации
	xVal := tensor.New(tensor.WithShape(len(input), vocabSize), tensor.Float32, tensor.WithBacking(flattenFloat32(input)))
	yVal := tensor.New(tensor.WithShape(len(target), vocabSize), tensor.Float32, tensor.WithBacking(flattenFloat32(target)))

	// Ввод значений в граф
	if err := gorgonia.Let(x, xVal); err != nil {
		log.Fatalf("Ошибка установки x: %v", err)
	}
	if err := gorgonia.Let(y, yVal); err != nil {
		log.Fatalf("Ошибка установки y: %v", err)
	}

	// Выполнение графа
	fmt.Println("Выполнение валидации...")
	if err := machine.RunAll(); err != nil {
		log.Fatalf("Ошибка выполнения графа: %v", err)
	}

	// Получение значения потерь
	lossVal, ok := loss.Value().Data().([]float32)
	if !ok {
		log.Fatalf("Не удалось преобразовать значение потерь")
	}
	fmt.Printf("Validation Loss: %.4f\n", lossVal[0])

	// Сброс машины
	machine.Reset()
}

// dialogue осуществляет диалог с ИИ
func dialogue(reader *bufio.Reader) {
	// Загрузка модели
	model := loadModel()

	// Загрузка словаря
	vocab := utils.LoadVocabulary(VocabFile)
	vocabSize := len(vocab.TokenToID)
	idToToken := make(map[int]string)
	for id, token := range vocab.IDToToken {
		idToToken[id] = token
	}

	// Инициализация графа
	g := gorgonia.NewGraph()

	// Определение узлов ввода
	x := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(1, vocabSize),
		gorgonia.WithName("x"))

	// Эмбеддинговый слой
	embedding := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(vocabSize, EmbeddingSize),
		gorgonia.WithName("Embedding"))

	// Скрытый слой
	hidden := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(EmbeddingSize, HiddenSize),
		gorgonia.WithName("Hidden"))

	// Выходной слой
	output := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(HiddenSize, vocabSize),
		gorgonia.WithName("Output"))

	// Параметры смещения
	bHidden := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(1, HiddenSize),
		gorgonia.WithName("bHidden"))

	bOutput := gorgonia.NewMatrix(g,
		tensor.Float32,
		gorgonia.WithShape(1, vocabSize),
		gorgonia.WithName("bOutput"))

	// Присвоение загруженных значений
	embeddingVal := tensor.New(tensor.WithShape(vocabSize, EmbeddingSize), tensor.Float32, tensor.WithBacking(model.Embedding))
	hiddenVal := tensor.New(tensor.WithShape(EmbeddingSize, HiddenSize), tensor.Float32, tensor.WithBacking(model.Hidden))
	outputVal := tensor.New(tensor.WithShape(HiddenSize, vocabSize), tensor.Float32, tensor.WithBacking(model.Output))
	bHiddenVal := tensor.New(tensor.WithShape(1, HiddenSize), tensor.Float32, tensor.WithBacking(model.bHidden))
	bOutputVal := tensor.New(tensor.WithShape(1, vocabSize), tensor.Float32, tensor.WithBacking(model.bOutput))

	if err := gorgonia.Let(embedding, embeddingVal); err != nil {
		log.Fatalf("Ошибка при установке Embedding: %v", err)
	}
	if err := gorgonia.Let(hidden, hiddenVal); err != nil {
		log.Fatalf("Ошибка при установке Hidden: %v", err)
	}
	if err := gorgonia.Let(output, outputVal); err != nil {
		log.Fatalf("Ошибка при установке Output: %v", err)
	}
	if err := gorgonia.Let(bHidden, bHiddenVal); err != nil {
		log.Fatalf("Ошибка при установке bHidden: %v", err)
	}
	if err := gorgonia.Let(bOutput, bOutputVal); err != nil {
		log.Fatalf("Ошибка при установке bOutput: %v", err)
	}

	// Векторизация входных данных через эмбеддинг
	embedded := must(gorgonia.Mul(x, embedding))
	embedded = must(gorgonia.Add(embedded, bHidden))

	// Прогноз через скрытый слой
	hiddenPred := must(gorgonia.Mul(embedded, hidden))
	hiddenPred = must(gorgonia.Add(hiddenPred, bOutput))

	// Прогноз
	pred := hiddenPred

	// Создание машины ленточных вычислений (Tape Machine)
	machine := gorgonia.NewTapeMachine(g)

	fmt.Println("Начните диалог с ИИ. Введите 'exit' для выхода.")

	for {
		fmt.Print("Вы: ")
		inputStr, err := reader.ReadString('\n')
		if err != nil {
			log.Fatalf("Ошибка при чтении ввода: %v", err)
		}
		inputStr = strings.TrimSpace(inputStr)
		if strings.ToLower(inputStr) == "exit" {
			break
		}

		// Токенизация и преобразование в ID
		tokens := utils.Tokenize(inputStr)
		if len(tokens) == 0 {
			fmt.Println("ИИ: <Нет ответа>")
			continue
		}

		// Берём последний токен для предсказания следующего
		lastToken := tokens[len(tokens)-1]
		inputID, exists := vocab.TokenToID[lastToken]
		if !exists {
			inputID = vocab.TokenToID["<UNK>"]
		}

		// One-hot кодирование
		inputSeq := make([]float32, vocabSize)
		inputSeq[inputID] = 1.0

		// Создание тензора
		xVal := tensor.New(tensor.WithShape(1, vocabSize), tensor.Float32, tensor.WithBacking(inputSeq))

		// Ввод значения в граф
		if err := gorgonia.Let(x, xVal); err != nil {
			log.Fatalf("Ошибка установки x: %v", err)
		}

		// Выполнение графа
		if err := machine.RunAll(); err != nil {
			log.Fatalf("Ошибка выполнения графа: %v", err)
		}

		// Получение предсказания
		predVal, ok := pred.Value().Data().([]float32)
		if !ok {
			log.Fatalf("Не удалось преобразовать предсказание")
		}

		// Поиск индекса с наибольшим значением
		maxIdx := 0
		maxProb := predVal[0]
		for i, prob := range predVal {
			if prob > maxProb {
				maxProb = prob
				maxIdx = i
			}
		}

		// Получение слова по ID
		response := "<UNK>"
		if token, exists := vocab.IDToToken[maxIdx]; exists {
			response = token
		}

		fmt.Printf("ИИ: %s\n", response)

		// Сброс машины для следующего шага
		machine.Reset()
	}
}


Cannot use 'tensor.Float32' (type Dtype) as the type ConsOpt

исправь