This commit is contained in:
che
2026-07-12 20:42:21 +05:00
parent b815cde04c
commit e3490e5d2e
23 changed files with 1568 additions and 538 deletions
+213 -12
View File
@@ -3,15 +3,16 @@ package control_case
import (
"encoding/csv"
"fmt"
"math"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"control/analize"
"github.com/che4web/go4rest"
"github.com/gin-gonic/gin"
"gonum.org/v1/gonum/dsp/fourier"
"gorm.io/gorm"
)
@@ -22,7 +23,7 @@ type ControlCaseController struct {
func NewControlCaseController(db *gorm.DB) *ControlCaseController {
viewSet := go4rest.NewViewSet[ControlCase](db)
viewSet.PreloadField = []string{"Params"}
viewSet.PreloadField = []string{"Params", "Params.InitialCondition"}
return &ControlCaseController{
ViewSet: viewSet,
@@ -35,9 +36,24 @@ type ParamsController struct {
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: go4rest.NewViewSet[Params](db),
ViewSet: viewSet,
db: db,
}
}
func NewInitialConditionController(db *gorm.DB) *InitialConditionController {
return &InitialConditionController{
ViewSet: go4rest.NewViewSet[InitialCondition](db),
db: db,
}
}
@@ -75,7 +91,7 @@ func (c *ControlCaseController) Launch(ctx *gin.Context) {
return
}
if err := c.db.Preload("Params").First(&controlCase, controlCase.ID).Error; err != nil {
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
}
@@ -88,6 +104,20 @@ type CSVSeriesResponse struct {
Rows [][]interface{} `json:"rows"`
}
type FFTPoint struct {
Frequency float64 `json:"frequency"`
Amplitude float64 `json:"amplitude"`
}
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 {
@@ -101,7 +131,7 @@ func (c *ControlCaseController) ChartData(ctx *gin.Context) {
return
}
csvPath := filepath.Join(".", strconv.FormatUint(uint64(controlCase.ParamsID), 10), "foo.csv")
csvPath := controlCase.FooCSVPath()
file, err := os.Open(csvPath)
if err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "csv file not found"})
@@ -138,7 +168,7 @@ func (c *ControlCaseController) ChartData(ctx *gin.Context) {
ctx.JSON(http.StatusOK, CSVSeriesResponse{Columns: columns, Rows: rows})
}
func (c *ControlCaseController) RecalculateAnalysis(ctx *gin.Context) {
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"})
@@ -146,19 +176,160 @@ func (c *ControlCaseController) RecalculateAnalysis(ctx *gin.Context) {
}
var controlCase ControlCase
if err := c.db.First(&controlCase, id).Error; err != nil {
if err := c.db.Preload("Params").First(&controlCase, id).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"})
return
}
csvPath := filepath.Join(".", strconv.FormatUint(uint64(controlCase.ParamsID), 10), "foo.csv")
result, err := analize.AnalyzeCSV(c.db, controlCase.ID, controlCase.Name, csvPath)
csvPath := controlCase.FooCSVPath()
file, err := os.Open(csvPath)
if err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "csv file not found"})
return
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if len(records) < 2 {
ctx.JSON(http.StatusOK, PSISpectrumResponse{Points: map[string][]FFTPoint{}})
return
}
ctx.JSON(http.StatusOK, result)
headers := make(map[string]int, len(records[0]))
for i, header := range records[0] {
headers[strings.ToLower(strings.TrimSpace(header))] = i
}
getIndex := func(name string) (int, error) {
idx, ok := headers[strings.ToLower(name)]
if !ok {
return -1, fmt.Errorf("missing column %q", name)
}
return idx, nil
}
tIndex, err := getIndex("t")
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
psiMIndex, err := getIndex("psi_m")
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
psiLIndex, err := getIndex("psi_l")
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
times := make([]float64, 0, len(records)-1)
psiM := make([]float64, 0, len(records)-1)
psiL := make([]float64, 0, len(records)-1)
for _, record := range records[1:] {
if len(record) <= maxInt(tIndex, psiMIndex, psiLIndex) {
continue
}
t, err := parseFloat(record[tIndex])
if err != nil {
continue
}
m, err := parseFloat(record[psiMIndex])
if err != nil {
continue
}
l, err := parseFloat(record[psiLIndex])
if err != nil {
continue
}
times = append(times, t)
psiM = append(psiM, m)
psiL = append(psiL, l)
}
timeStep := 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": buildSpectrumPoints(psiM, timeStep),
"psi_l": buildSpectrumPoints(psiL, timeStep),
},
})
}
func buildSpectrumPoints(values []float64, dt float64) []FFTPoint {
if len(values) < 2 || dt <= 0 {
return []FFTPoint{}
}
centered := make([]float64, len(values))
mean := 0.0
for _, v := range values {
mean += v
}
mean /= float64(len(values))
for i, v := range values {
centered[i] = v - mean
}
fft := fourier.NewFFT(len(centered))
coeffs := fft.Coefficients(nil, centered)
limit := len(coeffs) / 2
points := make([]FFTPoint, 0, limit+1)
for i := 0; i <= limit; i++ {
points = append(points, FFTPoint{
Frequency: float64(i) / (float64(len(centered)) * dt),
Amplitude: math.Hypot(real(coeffs[i]), imag(coeffs[i])) / float64(len(centered)),
})
}
return points
}
func parseFloat(value string) (float64, error) {
return strconv.ParseFloat(strings.TrimSpace(value), 64)
}
func maxInt(values ...int) int {
max := 0
for _, v := range values {
if v > max {
max = v
}
}
return max
}
func averageDelta(times []float64) float64 {
if len(times) < 2 {
return 0
}
total := 0.0
count := 0
for i := 1; i < len(times); i++ {
delta := times[i] - times[i-1]
if delta <= 0 {
continue
}
total += delta
count++
}
if count == 0 {
return 0
}
return total / float64(count)
}
func (c *ControlCaseController) FieldMap(ctx *gin.Context) {
@@ -180,8 +351,7 @@ func (c *ControlCaseController) FieldMap(ctx *gin.Context) {
return
}
h5Path := filepath.Join(".", strconv.FormatUint(uint64(controlCase.ParamsID), 10), "storage.h5")
result, err := ReadNearestFieldMap(h5Path, timeValue)
result, err := ReadNearestFieldMap(controlCase.StorageH5Path(), timeValue)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -189,3 +359,34 @@ func (c *ControlCaseController) FieldMap(ctx *gin.Context) {
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})
}