fix
This commit is contained in:
+27
-146
@@ -1,18 +1,18 @@
|
||||
package control_case
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"control/series"
|
||||
|
||||
"github.com/che4web/go4rest"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gonum.org/v1/gonum/dsp/fourier"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -104,10 +104,7 @@ type CSVSeriesResponse struct {
|
||||
Rows [][]interface{} `json:"rows"`
|
||||
}
|
||||
|
||||
type FFTPoint struct {
|
||||
Frequency float64 `json:"frequency"`
|
||||
Amplitude float64 `json:"amplitude"`
|
||||
}
|
||||
type FFTPoint = series.SpectrumPoint
|
||||
|
||||
type PSISpectrumResponse struct {
|
||||
TimeStep float64 `json:"time_step"`
|
||||
@@ -132,27 +129,17 @@ func (c *ControlCaseController) ChartData(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
csvPath := controlCase.FooCSVPath()
|
||||
file, err := os.Open(csvPath)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusNotFound, gin.H{"error": "csv file not found"})
|
||||
data, ok := readCSVResponse(ctx, csvPath)
|
||||
if !ok {
|
||||
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) == 0 {
|
||||
if len(data.Columns) == 0 {
|
||||
ctx.JSON(http.StatusOK, CSVSeriesResponse{Columns: []string{}, Rows: [][]interface{}{}})
|
||||
return
|
||||
}
|
||||
|
||||
columns := records[0]
|
||||
rows := make([][]interface{}, 0, len(records)-1)
|
||||
for _, record := range records[1:] {
|
||||
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)
|
||||
@@ -165,7 +152,7 @@ func (c *ControlCaseController) ChartData(ctx *gin.Context) {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, CSVSeriesResponse{Columns: columns, Rows: rows})
|
||||
ctx.JSON(http.StatusOK, CSVSeriesResponse{Columns: data.Columns, Rows: rows})
|
||||
}
|
||||
|
||||
func (c *ControlCaseController) PSISpectrum(ctx *gin.Context) {
|
||||
@@ -182,80 +169,23 @@ func (c *ControlCaseController) PSISpectrum(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
csvPath := controlCase.FooCSVPath()
|
||||
file, err := os.Open(csvPath)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusNotFound, gin.H{"error": "csv file not found"})
|
||||
data, ok := readCSVResponse(ctx, csvPath)
|
||||
if !ok {
|
||||
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 {
|
||||
if len(data.Rows) == 0 {
|
||||
ctx.JSON(http.StatusOK, PSISpectrumResponse{Points: map[string][]FFTPoint{}})
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
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]
|
||||
|
||||
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)
|
||||
timeStep := series.AverageDelta(times)
|
||||
if timeStep <= 0 {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid time step"})
|
||||
return
|
||||
@@ -264,72 +194,23 @@ func (c *ControlCaseController) PSISpectrum(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusOK, PSISpectrumResponse{
|
||||
TimeStep: timeStep,
|
||||
Points: map[string][]FFTPoint{
|
||||
"psi_m": buildSpectrumPoints(psiM, timeStep),
|
||||
"psi_l": buildSpectrumPoints(psiL, timeStep),
|
||||
"psi_m": series.SpectrumPoints(psiM, timeStep),
|
||||
"psi_l": series.SpectrumPoints(psiL, timeStep),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func buildSpectrumPoints(values []float64, dt float64) []FFTPoint {
|
||||
if len(values) < 2 || dt <= 0 {
|
||||
return []FFTPoint{}
|
||||
func readCSVResponse(ctx *gin.Context, csvPath string) (*series.CSVData, bool) {
|
||||
data, err := series.ReadCSV(csvPath)
|
||||
if err == nil {
|
||||
return data, true
|
||||
}
|
||||
|
||||
centered := make([]float64, len(values))
|
||||
mean := 0.0
|
||||
for _, v := range values {
|
||||
mean += v
|
||||
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()})
|
||||
}
|
||||
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)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *ControlCaseController) FieldMap(ctx *gin.Context) {
|
||||
|
||||
+14
-8
@@ -14,11 +14,14 @@ import (
|
||||
)
|
||||
|
||||
type ControlCase struct {
|
||||
gorm.Model
|
||||
Name string `json:"name"`
|
||||
ParamsID uint `json:"params_id"`
|
||||
Params Params `json:"params"`
|
||||
Status string `json:"status"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
Name string `json:"name"`
|
||||
ParamsID uint `json:"params_id"`
|
||||
Params Params `json:"params"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func (c ControlCase) FooCSVPath() string {
|
||||
@@ -59,9 +62,12 @@ type Params struct {
|
||||
}
|
||||
|
||||
type InitialCondition struct {
|
||||
gorm.Model
|
||||
Name string `json:"name"`
|
||||
FilePath string `json:"file_path"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
Name string `json:"name"`
|
||||
FilePath string `json:"file_path"`
|
||||
}
|
||||
|
||||
func (p *Params) ToTOML(filename string) error {
|
||||
|
||||
Reference in New Issue
Block a user