diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4be7730e0284abfcd06788553dccdea8f62cbc2b..330f0ba9de41f56fdd74aa8899163ff660ea07ba 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -150,6 +150,8 @@ collect-performance:
   - python3 ci/summarize-results.py --filter PredictBenchmark/PointSource results*.json result-summary-pointsource
   - python3 ci/summarize-results.py --filter PredictBenchmark/GaussianSource results*.json result-summary-gaussian
   - python3 ci/summarize-results.py --filter DirectionsBenchmark/Directions results*.json result-summary-directions
+  - python3 ci/summarize-results.py --filter SpectrumBenchmark/Spectrum results*.json result-summary-spectrum
+  
   
   
 
diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt
index d739d25f3db8d7ffb0592e2ff4f7f5721893e466..27847c8f4643b5990fdbef202df6118d1afb67da 100644
--- a/benchmark/CMakeLists.txt
+++ b/benchmark/CMakeLists.txt
@@ -14,7 +14,7 @@ FetchContent_MakeAvailable(googlebenchmark)
 
 set(BENCHMARKS simulator)
 
-add_executable(microbenchmarks main.cpp predict.cpp directions.cpp)
+add_executable(microbenchmarks main.cpp predict.cpp directions.cpp spectrum.cpp)
 
 target_compile_options(
   microbenchmarks PRIVATE $<$<CONFIG:Debug>:-g> $<$<CONFIG:Release>:-O3>
diff --git a/benchmark/spectrum.cpp b/benchmark/spectrum.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..295b2601c166bae5144563a2c9e7646e39d1d586
--- /dev/null
+++ b/benchmark/spectrum.cpp
@@ -0,0 +1,95 @@
+#include <Simulator.h>
+#include <benchmark/benchmark.h>
+#include <random>
+#include <xtensor/xtensor.hpp>
+
+#include <Direction.h>
+#include <PointSource.h>
+#include <Spectrum.h>
+
+const std::vector<int64_t> n_frequencies = {128, 256, 512};
+
+const double kHz = 1.e3;
+const double MHz = 1.e6;
+namespace {} // namespace
+
+class SpectrumBenchmark : public benchmark::Fixture {
+public:
+  void SetUp(benchmark::State &state) override {
+    n_frequencies = state.range(0);
+    const bool is_log = state.range(1);
+    const bool compute_rotation = state.range(2);
+    frequencies.resize({n_frequencies});
+    for (int i = 0; i < n_frequencies; i++) {
+      frequencies(i) = i * 100. * kHz + MHz;
+    }
+
+    spectrum = std::make_unique<Spectrum>();
+    auto &spec = *spectrum;
+    spec.has_logarithmic_spectral_index = is_log;
+    spec.reference_flux = {1.0, 0.0, 0.0, 0.0};
+    spec.spectral_terms = {1.0e-6, 2.0e-6, 3.0e-6};
+    spec.reference_frequency = MHz;
+    spec.polarization_angle = 0.3;
+    spec.polarization_factor = 0.2;
+    spec.rotation_measure = 0.1;
+    spec.has_rotation_measure = compute_rotation;
+    computed_spectrum = std::make_unique<StokesVector>();
+
+    point_source = std::make_unique<dp3::base::PointSource>(
+        dp3::base::Direction(0.0, 0.0), spec.reference_flux);
+
+    point_source->setSpectralTerms(
+        spec.reference_frequency, spec.has_logarithmic_spectral_index,
+        spec.spectral_terms.begin(), spec.spectral_terms.end());
+    if (compute_rotation) {
+      point_source->setRotationMeasure(spec.polarization_factor,
+                                       spec.polarization_angle,
+                                       spec.rotation_measure);
+    }
+  }
+  void TearDown(benchmark::State &) override { spectrum.reset(); }
+
+protected:
+  std::unique_ptr<Spectrum> spectrum;
+  std::unique_ptr<StokesVector> computed_spectrum;
+  size_t n_frequencies;
+  xt::xtensor<double, 1> frequencies;
+  std::unique_ptr<dp3::base::PointSource> point_source;
+};
+
+BENCHMARK_DEFINE_F(SpectrumBenchmark, SpectrumBenchmark)
+(benchmark::State &state) {
+  for (auto _ : state) {
+    spectrum->evaluate(frequencies, *computed_spectrum);
+  }
+}
+
+BENCHMARK_DEFINE_F(SpectrumBenchmark, SpectrumReference)
+(benchmark::State &state) {
+  for (auto _ : state) {
+    for (size_t i = 0; i < n_frequencies; i++) {
+      benchmark::DoNotOptimize(point_source->stokes(frequencies(i)).I);
+    }
+  }
+}
+
+BENCHMARK_REGISTER_F(SpectrumBenchmark, SpectrumBenchmark)
+    ->ArgsProduct({
+        n_frequencies,
+        {false, true},
+        {false, true},
+
+    })
+    ->ArgNames({"#frequencies", "is_log", "compute_rotation"})
+    ->Unit(benchmark::kNanosecond);
+
+BENCHMARK_REGISTER_F(SpectrumBenchmark, SpectrumReference)
+    ->ArgsProduct({
+        n_frequencies,
+        {false, true},
+        {false, true},
+
+    })
+    ->ArgNames({"#frequencies", "is_log", "compute_rotation"})
+    ->Unit(benchmark::kNanosecond);
diff --git a/include/PointSource.h b/include/PointSource.h
index 3bd6cfcc20b3a2af7cb569147da9d312105975d2..8703442a71941afb3db1664d6eccf469513ce0c8 100644
--- a/include/PointSource.h
+++ b/include/PointSource.h
@@ -13,8 +13,8 @@
 #include "Stokes.h"
 #include <Direction.h>
 
+#include <iostream>
 #include <memory>
-
 namespace dp3 {
 namespace base {
 
@@ -45,7 +45,7 @@ public:
   Stokes stokes() const;
   const std::vector<double> &spectrum() const { return itsSpectralTerms; }
   double referenceFreq() const { return itsRefFreq; }
-
+  double spectralTerm(size_t i) const { return itsSpectralTerms[i]; }
   void accept(ModelComponentVisitor &visitor) const override;
 
 private:
diff --git a/include/Spectrum.h b/include/Spectrum.h
index c464b90855712299eee74a38d197795fde2a4a56..94f2154f019d8f836dedaee610570549afe2052b 100644
--- a/include/Spectrum.h
+++ b/include/Spectrum.h
@@ -12,11 +12,13 @@
 #include <Stokes.h>
 #include <StokesVector.h>
 
+#include <casacore/casa/BasicSL/Constants.h>
 #include <xtensor/xtensor.hpp>
 
 using Stokes = dp3::base::Stokes;
 
-struct Spectrum {
+class Spectrum {
+public:
   Stokes reference_flux;
   xt::xtensor<double, 1> spectral_terms;
   double reference_frequency;
@@ -27,8 +29,76 @@ struct Spectrum {
   bool has_rotation_measure;
   bool has_logarithmic_spectral_index;
 
-  StokesVector evaluate(const xt::xtensor<double, 1> &frequencies) const {
-    return StokesVector();
+  void evaluate(const xt::xtensor<double, 1> &frequencies,
+                StokesVector &result) const {
+    result.I.resize(frequencies.size());
+    result.Q.resize(frequencies.size());
+    result.U.resize(frequencies.size());
+    result.V.resize(frequencies.size());
+
+    double I0 = reference_flux.I;
+
+    if (spectral_terms.size() > 0 && has_logarithmic_spectral_index) {
+      for (size_t f_idx = 0; f_idx < frequencies.size(); f_idx++) {
+        result.I[f_idx] = evaluate_log_spectrum(
+            I0, frequencies[f_idx], reference_frequency, spectral_terms);
+      }
+    } else if (spectral_terms.size()) {
+      for (size_t f_idx = 0; f_idx < frequencies.size(); f_idx++) {
+        result.I[f_idx] = evaluate_spectrum(
+            I0, frequencies[f_idx], reference_frequency, spectral_terms);
+      }
+    } else {
+      for (size_t f_idx = 0; f_idx < frequencies.size(); f_idx++) {
+        result.I[f_idx] = I0;
+      }
+    }
+
+    if (has_rotation_measure) {
+      // This way of computing is faster then using the xtensor extension
+      for (size_t f_idx = 0; f_idx < frequencies.size(); f_idx++) {
+        const double lambda = casacore::C::c / frequencies[f_idx];
+        const double chi =
+            2.0 * (polarization_angle + (rotation_measure * lambda * lambda));
+        const double stokesQU = result.I[f_idx] * polarization_factor;
+        result.Q[f_idx] = stokesQU * cos(chi);
+        result.U[f_idx] = stokesQU * sin(chi);
+      }
+    }
+  }
+
+private:
+  inline double
+  evaluate_polynomial(double x,
+                      const xt::xtensor<double, 1> &parameters) const {
+    double partial = 0.0;
+    for (auto it = spectral_terms.rbegin(), end = spectral_terms.rend();
+         it != end; ++it) {
+      partial = std::fma(partial, x, *it);
+    }
+    return partial;
+  }
+  inline double
+  evaluate_log_polynomial(double x,
+                          const xt::xtensor<double, 1> &parameters) const {
+    double base = log10(x);
+    return evaluate_polynomial(base, parameters);
+  }
+
+  inline double
+  evaluate_log_spectrum(double I0, const double frequency,
+                        const double reference_frequency,
+                        const xt::xtensor<double, 1> &parameters) const {
+    double x = frequency / reference_frequency;
+    return I0 * pow(10, x * evaluate_log_polynomial(x, parameters));
+  }
+  inline double
+  evaluate_spectrum(double I0, const double frequency,
+                    const double reference_frequency,
+                    const xt::xtensor<double, 1> &parameters) const {
+    double x = frequency / reference_frequency - 1.0;
+
+    return I0 + evaluate_polynomial(x, parameters) * x;
   }
 };
 
diff --git a/tests/test_spectrum.cpp b/tests/test_spectrum.cpp
index ba1fa273c3e2af7dba1bca80bbedb94fb92aef0c..74bdaeb95946af02140ec90317d7a021ae3a4362 100644
--- a/tests/test_spectrum.cpp
+++ b/tests/test_spectrum.cpp
@@ -3,11 +3,175 @@
 // SPDX-License-Identifier: Apache2.0
 
 #define BOOST_TEST_MODULE SPECTRUM_TEST
+#include <Direction.h>
+#include <PointSource.h>
 #include <Spectrum.h>
 #include <boost/test/unit_test.hpp>
 
 BOOST_AUTO_TEST_SUITE(SpectrumTestSuite)
 
-BOOST_AUTO_TEST_CASE(test_Spectrum) { BOOST_CHECK(true); }
+struct SpectrumFixture {
+  SpectrumFixture() {
+    spectrum.reference_flux = {1.0, 0.0, 0.0, 0.0};
+    spectrum.spectral_terms = {1.0, 2.0, 3.0};
+    spectrum.reference_frequency = 1.0e6;
+    spectrum.polarization_angle = 0.3;
+    spectrum.polarization_factor = 0.2;
+    spectrum.rotation_measure = 0.1;
+    spectrum.has_rotation_measure = false;
+    spectrum.has_logarithmic_spectral_index = false;
+  }
+  ~SpectrumFixture() { BOOST_TEST_MESSAGE("teardown fixture"); }
+
+  Spectrum spectrum;
+  xt::xtensor<double, 1> frequencies = {15.0e6, 20.0e6, 30.0e6};
+};
+
+BOOST_FIXTURE_TEST_CASE(normal_no_rotation, SpectrumFixture) {
+  dp3::base::PointSource point_source(dp3::base::Direction(0.0, 0.0),
+                                      spectrum.reference_flux);
+
+  point_source.setSpectralTerms(
+      spectrum.reference_frequency, spectrum.has_logarithmic_spectral_index,
+      spectrum.spectral_terms.begin(), spectrum.spectral_terms.end());
+  point_source.setRotationMeasure(0.0, 0.0, 0.0);
+
+  Stokes stokes_nu0(0, 0, 0, 0);
+  Stokes stokes_nu1(0, 0, 0, 0);
+  Stokes stokes_nu2(0, 0, 0, 0);
+
+  stokes_nu0 = point_source.stokes(frequencies[0]);
+  stokes_nu1 = point_source.stokes(frequencies[1]);
+  stokes_nu2 = point_source.stokes(frequencies[2]);
+
+  StokesVector result;
+  spectrum.evaluate(frequencies, result);
+
+  BOOST_CHECK_CLOSE(result.I[0], stokes_nu0.I, 1.e-6);
+  BOOST_CHECK_CLOSE(result.I[1], stokes_nu1.I, 1.e-6);
+  BOOST_CHECK_CLOSE(result.I[2], stokes_nu2.I, 1.e-6);
+  BOOST_CHECK_CLOSE(result.Q[0], stokes_nu0.Q, 1.e-6);
+  BOOST_CHECK_CLOSE(result.Q[1], stokes_nu1.Q, 1.e-6);
+  BOOST_CHECK_CLOSE(result.Q[2], stokes_nu2.Q, 1.e-6);
+  BOOST_CHECK_CLOSE(result.U[0], stokes_nu0.U, 1.e-6);
+  BOOST_CHECK_CLOSE(result.U[1], stokes_nu1.U, 1.e-6);
+  BOOST_CHECK_CLOSE(result.U[2], stokes_nu2.U, 1.e-6);
+  BOOST_CHECK_CLOSE(result.V[0], stokes_nu0.V, 1.e-6);
+  BOOST_CHECK_CLOSE(result.V[1], stokes_nu1.V, 1.e-6);
+  BOOST_CHECK_CLOSE(result.V[2], stokes_nu2.V, 1.e-6);
+}
+
+BOOST_FIXTURE_TEST_CASE(normal_with_rotation, SpectrumFixture) {
+  spectrum.has_rotation_measure = true;
+
+  dp3::base::PointSource point_source(dp3::base::Direction(0.0, 0.0),
+                                      spectrum.reference_flux);
+
+  point_source.setSpectralTerms(
+      spectrum.reference_frequency, spectrum.has_logarithmic_spectral_index,
+      spectrum.spectral_terms.begin(), spectrum.spectral_terms.end());
+  point_source.setRotationMeasure(spectrum.polarization_factor,
+                                  spectrum.polarization_angle,
+                                  spectrum.rotation_measure);
+  Stokes stokes_nu0(0, 0, 0, 0);
+  Stokes stokes_nu1(0, 0, 0, 0);
+  Stokes stokes_nu2(0, 0, 0, 0);
+
+  stokes_nu0 = point_source.stokes(frequencies[0]);
+  stokes_nu1 = point_source.stokes(frequencies[1]);
+  stokes_nu2 = point_source.stokes(frequencies[2]);
+
+  StokesVector result;
+  spectrum.evaluate(frequencies, result);
+
+  BOOST_CHECK_CLOSE(result.I[0], stokes_nu0.I, 1.e-6);
+  BOOST_CHECK_CLOSE(result.I[1], stokes_nu1.I, 1.e-6);
+  BOOST_CHECK_CLOSE(result.I[2], stokes_nu2.I, 1.e-6);
+  BOOST_CHECK_CLOSE(result.Q[0], stokes_nu0.Q, 1.e-6);
+  BOOST_CHECK_CLOSE(result.Q[1], stokes_nu1.Q, 1.e-6);
+  BOOST_CHECK_CLOSE(result.Q[2], stokes_nu2.Q, 1.e-6);
+  BOOST_CHECK_CLOSE(result.U[0], stokes_nu0.U, 1.e-6);
+  BOOST_CHECK_CLOSE(result.U[1], stokes_nu1.U, 1.e-6);
+  BOOST_CHECK_CLOSE(result.U[2], stokes_nu2.U, 1.e-6);
+  BOOST_CHECK_CLOSE(result.V[0], stokes_nu0.V, 1.e-6);
+  BOOST_CHECK_CLOSE(result.V[1], stokes_nu1.V, 1.e-6);
+  BOOST_CHECK_CLOSE(result.V[2], stokes_nu2.V, 1.e-6);
+}
+
+BOOST_FIXTURE_TEST_CASE(normal_log_no_rotation, SpectrumFixture) {
+  spectrum.spectral_terms = {1.0e-6, 2.0e-6, 3.0e-6};
+  spectrum.has_logarithmic_spectral_index = true;
+
+  dp3::base::PointSource point_source(dp3::base::Direction(0.0, 0.0),
+                                      spectrum.reference_flux);
+
+  point_source.setSpectralTerms(
+      spectrum.reference_frequency, spectrum.has_logarithmic_spectral_index,
+      spectrum.spectral_terms.begin(), spectrum.spectral_terms.end());
+  Stokes stokes_nu0(0, 0, 0, 0);
+  Stokes stokes_nu1(0, 0, 0, 0);
+  Stokes stokes_nu2(0, 0, 0, 0);
+
+  stokes_nu0 = point_source.stokes(frequencies[0]);
+  stokes_nu1 = point_source.stokes(frequencies[1]);
+  stokes_nu2 = point_source.stokes(frequencies[2]);
+
+  StokesVector result;
+  spectrum.evaluate(frequencies, result);
+
+  BOOST_CHECK_CLOSE_FRACTION(result.I[0], stokes_nu0.I, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.I[1], stokes_nu1.I, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.I[2], stokes_nu2.I, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.Q[0], stokes_nu0.Q, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.Q[1], stokes_nu1.Q, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.Q[2], stokes_nu2.Q, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.U[0], stokes_nu0.U, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.U[1], stokes_nu1.U, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.U[2], stokes_nu2.U, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.V[0], stokes_nu0.V, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.V[1], stokes_nu1.V, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.V[2], stokes_nu2.V, 1.e-3);
+}
+
+BOOST_FIXTURE_TEST_CASE(normal_log_with_rotation, SpectrumFixture) {
+  spectrum.spectral_terms = {1.0e-6, 2.0e-6, 3.0e-6};
+  spectrum.has_logarithmic_spectral_index = true;
+  spectrum.has_rotation_measure = true;
+  spectrum.has_logarithmic_spectral_index = true;
+
+  dp3::base::PointSource point_source(dp3::base::Direction(0.0, 0.0),
+                                      spectrum.reference_flux);
+
+  point_source.setSpectralTerms(
+      spectrum.reference_frequency, spectrum.has_logarithmic_spectral_index,
+      spectrum.spectral_terms.begin(), spectrum.spectral_terms.end());
+
+  point_source.setRotationMeasure(spectrum.polarization_factor,
+                                  spectrum.polarization_angle,
+                                  spectrum.rotation_measure);
+  Stokes stokes_nu0(0, 0, 0, 0);
+  Stokes stokes_nu1(0, 0, 0, 0);
+  Stokes stokes_nu2(0, 0, 0, 0);
+
+  stokes_nu0 = point_source.stokes(frequencies[0]);
+  stokes_nu1 = point_source.stokes(frequencies[1]);
+  stokes_nu2 = point_source.stokes(frequencies[2]);
+
+  StokesVector result;
+  spectrum.evaluate(frequencies, result);
+
+  BOOST_CHECK_CLOSE_FRACTION(result.I[0], stokes_nu0.I, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.I[1], stokes_nu1.I, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.I[2], stokes_nu2.I, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.Q[0], stokes_nu0.Q, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.Q[1], stokes_nu1.Q, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.Q[2], stokes_nu2.Q, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.U[0], stokes_nu0.U, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.U[1], stokes_nu1.U, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.U[2], stokes_nu2.U, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.V[0], stokes_nu0.V, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.V[1], stokes_nu1.V, 1.e-3);
+  BOOST_CHECK_CLOSE_FRACTION(result.V[2], stokes_nu2.V, 1.e-3);
+}
 
 BOOST_AUTO_TEST_SUITE_END()
\ No newline at end of file