package control_case import ( "fmt" "os/exec" "regexp" "strconv" "strings" ) type FieldMapResponse struct { RequestedT float64 `json:"requested_t"` StageT float64 `json:"stage_t"` Rows int `json:"rows"` Cols int `json:"cols"` Fields map[string][][]float64 `json:"fields"` } var ( stagePathRe = regexp.MustCompile(`^\s*group\s+/map/(stage_t=[^\s]+)\s*$`) dimsRe = regexp.MustCompile(`\(\s*(\d+)\s*,\s*(\d+)\s*\)`) floatInDataRe = regexp.MustCompile(`[-+]?(?:\d*\.\d+|\d+\.?\d*)(?:[eE][-+]?\d+)?`) ) func ReadNearestFieldMap(filePath string, requestedT float64) (*FieldMapResponse, error) { stageGroups, err := listStageGroups(filePath) if err != nil { return nil, err } if len(stageGroups) == 0 { return nil, fmt.Errorf("no stage_t groups found") } selected := stageGroups[0] for _, stage := range stageGroups[1:] { if absFloat64(stage.stageT-requestedT) < absFloat64(selected.stageT-requestedT) { selected = stage } } result := &FieldMapResponse{ RequestedT: requestedT, StageT: selected.stageT, Fields: make(map[string][][]float64, 4), } for _, fieldName := range []string{"psi", "phi", "T", "C"} { dims, values, err := readDatasetMatrix(filePath, selected.path, fieldName) if err != nil { return nil, err } if len(dims) != 2 { return nil, fmt.Errorf("unexpected dims for %s: %v", fieldName, dims) } matrix, err := reshapeFloat64(values, dims[0], dims[1]) if err != nil { return nil, fmt.Errorf("failed to reshape dataset %s: %w", fieldName, err) } matrix = transposeFloat64(matrix) if result.Rows == 0 && result.Cols == 0 { result.Rows = len(matrix) if result.Rows > 0 { result.Cols = len(matrix[0]) } } result.Fields[fieldName] = matrix } return result, nil } type stageFieldMap struct { path string stageT float64 } func listStageGroups(filePath string) ([]stageFieldMap, error) { out, err := runH5Dump(filePath, "-n") if err != nil { return nil, err } var stages []stageFieldMap for _, line := range strings.Split(string(out), "\n") { matches := stagePathRe.FindStringSubmatch(line) if len(matches) != 2 { continue } stageT, err := strconv.ParseFloat(strings.TrimPrefix(matches[1], "stage_t="), 64) if err != nil { continue } stages = append(stages, stageFieldMap{ path: "/map/" + matches[1], stageT: stageT, }) } return stages, nil } func readDatasetMatrix(filePath, stagePath, fieldName string) ([]int, []float64, error) { datasetPath := stagePath + "/" + fieldName headerOut, err := runH5Dump(filePath, "-H", "-d", datasetPath) if err != nil { return nil, nil, err } rows, cols, err := parseDimsFromHeader(headerOut) if err != nil { return nil, nil, fmt.Errorf("failed to parse dims for %s: %w", datasetPath, err) } dataOut, err := runH5Dump(filePath, "-d", datasetPath) if err != nil { return nil, nil, err } values, err := parseFloatData(dataOut) if err != nil { return nil, nil, fmt.Errorf("failed to parse data for %s: %w", datasetPath, err) } if rows*cols != len(values) { return nil, nil, fmt.Errorf("value count %d does not match %dx%d for %s", len(values), rows, cols, datasetPath) } return []int{rows, cols}, values, nil } func parseDimsFromHeader(out []byte) (int, int, error) { matches := dimsRe.FindSubmatch(out) if len(matches) != 3 { return 0, 0, fmt.Errorf("dimensions not found") } rows, err := strconv.Atoi(string(matches[1])) if err != nil { return 0, 0, err } cols, err := strconv.Atoi(string(matches[2])) if err != nil { return 0, 0, err } return rows, cols, nil } func parseFloatData(out []byte) ([]float64, error) { lines := strings.Split(string(out), "\n") values := make([]float64, 0, 2048) inData := false for _, line := range lines { trimmed := strings.TrimSpace(line) switch { case trimmed == "DATA {": inData = true continue case inData && trimmed == "}": return values, nil case !inData: continue } colon := strings.Index(trimmed, ":") if colon < 0 { continue } payload := strings.TrimSpace(trimmed[colon+1:]) if payload == "" { continue } matches := floatInDataRe.FindAllString(payload, -1) for _, match := range matches { value, err := strconv.ParseFloat(match, 64) if err != nil { return nil, err } values = append(values, value) } } if !inData { return nil, fmt.Errorf("data block not found") } return nil, fmt.Errorf("data block not terminated") } func reshapeFloat64(values []float64, rows, cols int) ([][]float64, error) { if rows*cols != len(values) { return nil, fmt.Errorf("value count %d does not match %dx%d", len(values), rows, cols) } matrix := make([][]float64, rows) for r := 0; r < rows; r++ { start := r * cols row := make([]float64, cols) copy(row, values[start:start+cols]) matrix[r] = row } return matrix, nil } func transposeFloat64(matrix [][]float64) [][]float64 { if len(matrix) == 0 || len(matrix[0]) == 0 { return matrix } rows := len(matrix) cols := len(matrix[0]) transposed := make([][]float64, cols) for c := 0; c < cols; c++ { transposed[c] = make([]float64, rows) for r := 0; r < rows; r++ { transposed[c][r] = matrix[r][c] } } return transposed } func runH5Dump(filePath string, args ...string) ([]byte, error) { cmdArgs := append(args, filePath) cmd := exec.Command("h5dump", cmdArgs...) out, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("h5dump %v failed: %w: %s", cmdArgs, err, strings.TrimSpace(string(out))) } return out, nil } func absFloat64(value float64) float64 { if value < 0 { return -value } return value }