← All tasks
javacodex/java-t1 #44Not a task: repair changed code

FFT Spectrum Analyzer (java, written by Codex)

envgap__codex__java-t1-44

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

NullPointerException: Map.of() with null filter/export/file config values
Not a benchmark task.
  • Its repair changed source code, so it is not an environment task.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
pom.xml
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/java-t1 #44 · read the task the agent was given
Codex wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: FFT Spectrum Analyzer

Write a program that performs Fast Fourier Transform (FFT) analysis on time-domain signal data, identifying dominant frequencies, computing power spectral density, and supporting windowing functions.

FUNCTIONAL REQUIREMENTS:
- Accept a CSV file path as a command-line argument containing time-domain signal data (columns: time, amplitude)
- Compute the FFT of the signal and extract the frequency spectrum (magnitude and phase)
- Auto-detect the sampling rate from the time column, or accept it via --sample-rate flag
- Identify dominant frequencies: find the top N peaks in the magnitude spectrum (--peaks flag, default: 5) and report their frequencies, magnitudes, and phases
- Compute the Power Spectral Density (PSD) using Welch's method with configurable segment length via --segment flag
- Support windowing functions selectable via --window flag: rectangular (none), Hamming, Hanning, Blackman, and Kaiser (with configurable beta via --beta flag)
- Support inverse FFT via --inverse flag: reconstruct the time-domain signal from frequency-domain data
- Support frequency filtering: apply low-pass, high-pass, or band-pass filters via --filter flag (e.g., --filter low:1000 for 1kHz low-pass) and output the filtered signal
- Export the frequency spectrum data as CSV via --export flag
- Print analysis summary to console: sampling rate, number of samples, frequency resolution, dominant frequencies with magnitudes, and total signal power
- Save the full analysis as JSON with --output flag (default: fft_analysis.json)
- If no input is given, generate a sample signal composed of three sine waves at known frequencies (100Hz, 250Hz, 800Hz) with added white noise, sampled at 4000Hz for 1 second, analyze it, and show that the FFT correctly identifies the three component frequencies
- Handle errors: non-uniform sampling, insufficient data points, and signals with DC offset

Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.tmlr</groupId>
  <artifactId>fft-spectrum-analyzer</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.7.1</version>
        <configuration>
          <archive>
            <manifest>
              <mainClass>FftSpectrumAnalyzer</mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
README.md
# FFT Spectrum Analyzer (Java)

## Requirements
- Ubuntu 22.04
- JDK 17+
- Maven 3.8+

## Build
```bash
mvn -q -DskipTests package assembly:single
```

## Run
```bash
java -cp target/fft-spectrum-analyzer-1.0.0-jar-with-dependencies.jar FftSpectrumAnalyzer signal.csv --peaks 5 --window hamming
java -cp target/fft-spectrum-analyzer-1.0.0-jar-with-dependencies.jar FftSpectrumAnalyzer signal.csv --sample-rate 4000 --segment 512 --filter low:1000 --export spectrum.csv
java -cp target/fft-spectrum-analyzer-1.0.0-jar-with-dependencies.jar FftSpectrumAnalyzer signal.csv --inverse --output fft_analysis.json
```

If no input is given, a synthetic signal is generated (100Hz, 250Hz, 800Hz + noise).
src/main/java/FftSpectrumAnalyzer.java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.time.Instant;
import java.util.*;

public class FftSpectrumAnalyzer {
    private static class Config {
        String file;
        Double sampleRate;
        int peaks = 5;
        int segment = 256;
        String window = "rectangular";
        double beta = 8.0;
        boolean inverse;
        String filter;
        String export;
        String output = "fft_analysis.json";
    }

    public static void main(String[] args) {
        try {
            Config cfg = parseArgs(args);
            if (cfg.file == null) {
                cfg.file = "sample_signal.csv";
                writeSample(Paths.get(cfg.file));
            }

            Signal s = readSignal(Paths.get(cfg.file));
            double sr = cfg.sampleRate != null ? cfg.sampleRate : autoSampleRate(s.t);

            double[] win = buildWindow(cfg.window, s.x.length, cfg.beta);
            double[] x = new double[s.x.length];
            for (int i = 0; i < x.length; i++) x[i] = s.x[i] * win[i];

            Complex[] X = fft(x);
            Spectrum spec = spectrum(X, sr);
            List<Map<String, Object>> peaks = topPeaks(spec, cfg.peaks);
            Psd psd = welch(x, sr, cfg.segment, cfg.window, cfg.beta);

            double[] reconstructed = cfg.inverse ? ifftReal(X) : null;
            double[] filtered = null;
            if (cfg.filter != null) {
                Complex[] Xf = applyFilter(X, sr, cfg.filter);
                filtered = ifftReal(Xf);
            }

            if (cfg.export != null) {
                StringBuilder sb = new StringBuilder("frequency,magnitude,phase\n");
                for (int i = 0; i < spec.freq.length; i++) sb.append(spec.freq[i]).append(',').append(spec.mag[i]).append(',').append(spec.phase[i]).append('\n');
                Files.writeString(Paths.get(cfg.export), sb.toString(), StandardCharsets.UTF_8);
            }

            double power = 0;
            for (double v : s.x) power += v * v;
            power /= s.x.length;

            Map<String, Object> report = new LinkedHashMap<>();
            report.put("generatedAt", Instant.now().toString());
            report.put("config", Map.of(
                "file", cfg.file,
                "sampleRate", cfg.sampleRate,
                "peaks", cfg.peaks,
                "segment", cfg.segment,
                "window", cfg.window,
                "beta", cfg.beta,
                "inverse", cfg.inverse,
                "filter", cfg.filter,
                "export", cfg.export,
                "output", cfg.output
            ));
            report.put("samplingRate", sr);
            report.put("samples", s.x.length);
            report.put("frequencyResolution", sr / s.x.length);
            report.put("dominantFrequencies", peaks);
            report.put("totalSignalPower", power);
            report.put("spectrum", Map.of("frequency", toList(spec.freq), "magnitude", toList(spec.mag), "phase", toList(spec.phase)));
            report.put("psd", Map.of("frequency", toList(psd.freq), "power", toList(psd.pxx)));
            report.put("reconstructed", reconstructed == null ? null : toList(reconstructed));
            report.put("filteredSignal", filtered == null ? null : toList(filtered));

            System.out.printf("Sampling rate: %.3f Hz%n", sr);
            System.out.println("Samples: " + s.x.length);
            System.out.printf("Resolution: %.6f Hz%n", sr / s.x.length);
            System.out.println("Top peaks:");
            for (int i = 0; i < peaks.size(); i++) {
                var p = peaks.get(i);
                System.out.printf("%d. f=%.3fHz mag=%.5f phase=%.4f%n", i + 1, p.get("frequency"), p.get("magnitude"), p.get("phase"));
            }

            Files.writeString(Paths.get(cfg.output), toJson(report), StandardCharsets.UTF_8);
        } catch (Exception ex) {
            System.err.println("Error: " + ex.getMessage());
            System.exit(1);
        }
    }

    private record Signal(double[] t, double[] x) {}
    private record Complex(double re, double im) {}
    private record Spectrum(double[] freq, double[] mag, double[] phase) {}
    private record Psd(double[] freq, double[] pxx) {}

    private static Config parseArgs(String[] args) {
        Config cfg = new Config();
        List<String> pos = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            String a = args[i];
            if (!a.startsWith("--")) { pos.add(a); continue; }
            switch (a) {
                case "--sample-rate" -> cfg.sampleRate = Double.parseDouble(args[++i]);
                case "--peaks" -> cfg.peaks = Integer.parseInt(args[++i]);
                case "--segment" -> cfg.segment = Integer.parseInt(args[++i]);
                case "--window" -> cfg.window = args[++i];
                case "--beta" -> cfg.beta = Double.parseDouble(args[++i]);
                case "--inverse" -> cfg.inverse = true;
                case "--filter" -> cfg.filter = args[++i];
                case "--export" -> cfg.export = args[++i];
                case "--output" -> cfg.output = args[++i];
                default -> throw new IllegalArgumentException("Unknown option: " + a);
            }
        }
        if (!pos.isEmpty()) cfg.file = pos.get(0);
        return cfg;
    }

    private static Signal readSignal(Path p) throws IOException {
        List<String> lines = Files.readAllLines(p, StandardCharsets.UTF_8);
        double[] t = new double[lines.size() - 1];
        double[] x = new double[lines.size() - 1];
        for (int i = 1; i < lines.size(); i++) {
            String[] parts = lines.get(i).split(",");
            t[i - 1] = Double.parseDouble(parts[0].trim());
            x[i - 1] = Double.parseDouble(parts[1].trim());
        }
        return new Signal(t, x);
    }

    private static void writeSample(Path p) throws IOException {
        int sr = 4000;
        int N = 4000;
        StringBuilder sb = new StringBuilder("time,amplitude\n");
        Random rnd = new Random(42);
        for (int i = 0; i < N; i++) {
            double t = i / (double) sr;
            double x = 1.2 * Math.sin(2 * Math.PI * 100 * t)
                + 0.8 * Math.sin(2 * Math.PI * 250 * t + 0.7)
                + 0.5 * Math.sin(2 * Math.PI * 800 * t + 1.2)
                + (rnd.nextDouble() - 0.5) * 0.35;
            sb.append(String.format(Locale.ROOT, "%.6f,%.8f%n", t, x));
        }
        Files.writeString(p, sb.toString(), StandardCharsets.UTF_8);
    }

    private static double autoSampleRate(double[] t) {
        if (t.length < 2) throw new IllegalArgumentException("Insufficient data points");
        double[] dt = new double[t.length - 1];
        for (int i = 1; i < t.length; i++) dt[i - 1] = t[i] - t[i - 1];
        double mean = Arrays.stream(dt).average().orElseThrow();
        double var = 0;
        for (double v : dt) var += (v - mean) * (v - mean);
        var /= dt.length;
        if (Math.sqrt(var) > 0.05 * mean) throw new IllegalArgumentException("Non-uniform sampling detected");
        return 1.0 / mean;
    }

    private static double[] buildWindow(String name, int N, double beta) {
        double[] w = new double[N];
        for (int n = 0; n < N; n++) {
            switch (name) {
                case "hamming" -> w[n] = 0.54 - 0.46 * Math.cos((2 * Math.PI * n) / (N - 1));
                case "hanning" -> w[n] = 0.5 * (1 - Math.cos((2 * Math.PI * n) / (N - 1)));
                case "blackman" -> w[n] = 0.42 - 0.5 * Math.cos((2 * Math.PI * n) / (N - 1)) + 0.08 * Math.cos((4 * Math.PI * n) / (N - 1));
                case "kaiser" -> {
                    double alpha = (N - 1) / 2.0;
                    double r = (n - alpha) / alpha;
                    w[n] = besselI0(beta * Math.sqrt(1 - r * r)) / besselI0(beta);
                }
                default -> w[n] = 1.0;
            }
        }
        return w;
    }

    private static double besselI0(double x) {
        double sum = 1, term = 1;
        for (int k = 1; k < 25; k++) {
            term *= (x * x) / (4.0 * k * k);
            sum += term;
        }
        return sum;
    }

    private static Complex[] fft(double[] x) {
        int N = x.length;
        Complex[] X = new Complex[N];
        for (int k = 0; k < N; k++) {
            double re = 0, im = 0;
            for (int n = 0; n < N; n++) {
                double ang = -2 * Math.PI * k * n / N;
                re += x[n] * Math.cos(ang);
                im += x[n] * Math.sin(ang);
            }
            X[k] = new Complex(re, im);
        }
        return X;
    }

    private static double[] ifftReal(Complex[] X) {
        int N = X.length;
        double[] x = new double[N];
        for (int n = 0; n < N; n++) {
            double re = 0;
            for (int k = 0; k < N; k++) {
                double ang = 2 * Math.PI * k * n / N;
                re += X[k].re * Math.cos(ang) - X[k].im * Math.sin(ang);
            }
            x[n] = re / N;
        }
        return x;
    }

    private static Spectrum spectrum(Complex[] X, double sr) {
        int N = X.length;
        int half = N / 2;
        double[] f = new double[half + 1];
        double[] m = new double[half + 1];
        double[] p = new double[half + 1];
        for (int k = 0; k <= half; k++) {
            f[k] = k * sr / N;
            m[k] = Math.hypot(X[k].re, X[k].im);
            p[k] = Math.atan2(X[k].im, X[k].re);
        }
        return new Spectrum(f, m, p);
    }

    private static List<Map<String, Object>> topPeaks(Spectrum s, int n) {
        List<Integer> idx = new ArrayList<>();
        for (int i = 1; i < s.mag.length - 1; i++) if (s.mag[i] > s.mag[i - 1] && s.mag[i] > s.mag[i + 1]) idx.add(i);
        idx.sort((a, b) -> Double.compare(s.mag[b], s.mag[a]));
        List<Map<String, Object>> out = new ArrayList<>();
        for (int i = 0; i < Math.min(n, idx.size()); i++) {
            int k = idx.get(i);
            out.add(Map.of("frequency", s.freq[k], "magnitude", s.mag[k], "phase", s.phase[k]));
        }
        return out;
    }

    private static Psd welch(double[] x, double sr, int seg, String window, double beta) {
        int segLen = Math.min(seg, x.length);
        int step = Math.max(1, segLen / 2);
        int bins = segLen / 2 + 1;
        double[] acc = new double[bins];
        int count = 0;
        for (int start = 0; start + segLen <= x.length; start += step) {
            double[] w = buildWindow(window, segLen, beta);
            double[] segx = new double[segLen];
            for (int i = 0; i < segLen; i++) segx[i] = x[start + i] * w[i];
            Complex[] X = fft(segx);
            for (int k = 0; k < bins; k++) {
                double p = (X[k].re * X[k].re + X[k].im * X[k].im) / (segLen * sr);
                acc[k] += p;
            }
            count++;
        }
        for (int i = 0; i < acc.length; i++) acc[i] /= Math.max(1, count);
        double[] f = new double[bins];
        for (int k = 0; k < bins; k++) f[k] = k * sr / segLen;
        return new Psd(f, acc);
    }

    private static Complex[] applyFilter(Complex[] X, double sr, String spec) {
        int N = X.length;
        int half = N / 2;
        Complex[] out = Arrays.copyOf(X, X.length);

        for (int k = 0; k <= half; k++) {
            double f = k * sr / N;
            boolean keep = true;
            if (spec.startsWith("low:")) keep = f <= Double.parseDouble(spec.substring(4));
            else if (spec.startsWith("high:")) keep = f >= Double.parseDouble(spec.substring(5));
            else if (spec.startsWith("band:")) {
                String[] lr = spec.substring(5).split("-");
                double l = Double.parseDouble(lr[0]);
                double h = Double.parseDouble(lr[1]);
                keep = f >= l && f <= h;
            }
            if (!keep) {
                out[k] = new Complex(0, 0);
                if (k != 0 && k != half) out[N - k] = new Complex(0, 0);
            }
        }
        return out;
    }

    private static List<Double> toList(double[] a) {
        List<Double> l = new ArrayList<>(a.length);
        for (double v : a) l.add(v);
        return l;
    }

    private static String toJson(Object obj) {
        if (obj == null) return "null";
        if (obj instanceof String s) return '"' + s.replace("\\", "\\\\").replace("\"", "\\\"") + '"';
        if (obj instanceof Number || obj instanceof Boolean) return obj.toString();
        if (obj instanceof Map<?, ?> m) {
            StringBuilder sb = new StringBuilder("{");
            boolean first = true;
            for (Map.Entry<?, ?> e : m.entrySet()) {
                if (!first) sb.append(',');
                first = false;
                sb.append(toJson(String.valueOf(e.getKey()))).append(':').append(toJson(e.getValue()));
            }
            sb.append('}');
            return sb.toString();
        }
        if (obj instanceof Iterable<?> it) {
            StringBuilder sb = new StringBuilder("[");
            boolean first = true;
            for (Object x : it) {
                if (!first) sb.append(',');
                first = false;
                sb.append(toJson(x));
            }
            sb.append(']');
            return sb.toString();
        }
        return toJson(String.valueOf(obj));
    }
}