Files
control/control_case/controllers.go
T
2026-07-15 09:41:56 +05:00

321 lines
8.4 KiB
Go

package control_case
import (
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"control/series"
"github.com/che4web/go4rest"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ControlCaseController struct {
*go4rest.ViewSet[ControlCase]
db *gorm.DB
}
func NewControlCaseController(db *gorm.DB) *ControlCaseController {
viewSet := go4rest.NewViewSet[ControlCase](db)
viewSet.PreloadField = []string{"Params", "Params.InitialCondition"}
return &ControlCaseController{
ViewSet: viewSet,
db: db,
}
}
type ParamsController struct {
*go4rest.ViewSet[Params]
db *gorm.DB
}
type InitialConditionController struct {
*go4rest.ViewSet[InitialCondition]
db *gorm.DB
}
func NewParamsController(db *gorm.DB) *ParamsController {
viewSet := go4rest.NewViewSet[Params](db)
viewSet.PreloadField = []string{"InitialCondition"}
return &ParamsController{
ViewSet: viewSet,
db: db,
}
}
func NewInitialConditionController(db *gorm.DB) *InitialConditionController {
return &InitialConditionController{
ViewSet: go4rest.NewViewSet[InitialCondition](db),
db: db,
}
}
type LaunchControlCaseRequest struct {
ParamsID uint `json:"params_id" binding:"required"`
Name string `json:"name"`
}
func (c *ControlCaseController) Launch(ctx *gin.Context) {
var req LaunchControlCaseRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var params Params
if err := c.db.First(&params, req.ParamsID).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "params not found"})
return
}
name := req.Name
if strings.TrimSpace(name) == "" {
name = fmt.Sprintf("case-%d", params.ID)
}
controlCase := ControlCase{
Name: name,
ParamsID: params.ID,
Status: "N",
}
if err := c.db.Create(&controlCase).Error; err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if err := c.db.Preload("Params").Preload("Params.InitialCondition").First(&controlCase, controlCase.ID).Error; err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusCreated, controlCase)
}
func (c *ControlCaseController) Restart(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
var controlCase ControlCase
if err := c.db.Preload("Params").Preload("Params.InitialCondition").First(&controlCase, id).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"})
return
}
if controlCase.Status == "N" {
ctx.JSON(http.StatusConflict, gin.H{"error": "calculation is already queued or running"})
return
}
if controlCase.ParamsID == 0 {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "params_id is empty"})
return
}
folderPath := filepath.Join(BASE_DIR, fmt.Sprintf("%d", controlCase.ParamsID))
if err := os.RemoveAll(folderPath); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if err := c.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Exec("DELETE FROM analizes WHERE case_id = ?", controlCase.ID).Error; err != nil {
return err
}
return tx.Model(&ControlCase{}).Where("id = ?", controlCase.ID).Update("status", "N").Error
}); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if err := c.db.Preload("Params").Preload("Params.InitialCondition").First(&controlCase, controlCase.ID).Error; err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, controlCase)
}
type CSVSeriesResponse struct {
Columns []string `json:"columns"`
Rows [][]interface{} `json:"rows"`
}
type FFTPoint = series.SpectrumPoint
type PSISpectrumResponse struct {
TimeStep float64 `json:"time_step"`
Points map[string][]FFTPoint `json:"points"`
}
type InitialConditionResponse struct {
InitialCondition InitialCondition `json:"initial_condition"`
}
func (c *ControlCaseController) ChartData(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
var controlCase ControlCase
if err := c.db.Preload("Params").First(&controlCase, id).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"})
return
}
csvPath := controlCase.FooCSVPath()
data, ok := readCSVResponse(ctx, csvPath)
if !ok {
return
}
if len(data.Columns) == 0 {
ctx.JSON(http.StatusOK, CSVSeriesResponse{Columns: []string{}, Rows: [][]interface{}{}})
return
}
rows := make([][]interface{}, 0, len(data.Rows))
for _, record := range data.Rows {
row := make([]interface{}, 0, len(record))
for _, value := range record {
trimmed := strings.TrimSpace(value)
if f, err := strconv.ParseFloat(trimmed, 64); err == nil {
row = append(row, f)
} else {
row = append(row, trimmed)
}
}
rows = append(rows, row)
}
ctx.JSON(http.StatusOK, CSVSeriesResponse{Columns: data.Columns, Rows: rows})
}
func (c *ControlCaseController) PSISpectrum(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
var controlCase ControlCase
if err := c.db.Preload("Params").First(&controlCase, id).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"})
return
}
csvPath := controlCase.FooCSVPath()
data, ok := readCSVResponse(ctx, csvPath)
if !ok {
return
}
if len(data.Rows) == 0 {
ctx.JSON(http.StatusOK, PSISpectrumResponse{Points: map[string][]FFTPoint{}})
return
}
columns, err := data.NumericColumns("t", "psi_m", "psi_l")
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
times, psiM, psiL := columns[0], columns[1], columns[2]
timeStep := series.AverageDelta(times)
if timeStep <= 0 {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid time step"})
return
}
ctx.JSON(http.StatusOK, PSISpectrumResponse{
TimeStep: timeStep,
Points: map[string][]FFTPoint{
"psi_m": series.SpectrumPoints(psiM, timeStep),
"psi_l": series.SpectrumPoints(psiL, timeStep),
},
})
}
func readCSVResponse(ctx *gin.Context, csvPath string) (*series.CSVData, bool) {
data, err := series.ReadCSV(csvPath)
if err == nil {
return data, true
}
if errors.Is(err, os.ErrNotExist) {
ctx.JSON(http.StatusNotFound, gin.H{"error": "csv file not found"})
} else {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
return nil, false
}
func (c *ControlCaseController) FieldMap(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
timeValue, err := strconv.ParseFloat(ctx.Query("time"), 64)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid time"})
return
}
var controlCase ControlCase
if err := c.db.First(&controlCase, id).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"})
return
}
result, err := ReadNearestFieldMap(controlCase.StorageH5Path(), timeValue)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, result)
}
func (c *ControlCaseController) CreateInitialCondition(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
var controlCase ControlCase
if err := c.db.First(&controlCase, id).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"})
return
}
outputPath := filepath.Join(BASE_DIR, "initial_conditions", fmt.Sprintf("control_case_%d", controlCase.ID), "storage.h5")
if err := controlCase.CreateStorageH5FromLastState(outputPath); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
initialCondition := InitialCondition{
Name: fmt.Sprintf("control-case-%d", controlCase.ID),
FilePath: outputPath,
}
if err := c.db.Where("file_path = ?", outputPath).Assign(initialCondition).FirstOrCreate(&initialCondition).Error; err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, InitialConditionResponse{InitialCondition: initialCondition})
}