diff --git a/optimization/optimizers/CMakeLists.txt b/optimization/optimizers/CMakeLists.txt
index bd410e1e04c085e98eecb25403a0d81f50545f2f..c585fe9762a67535cd384d04adbaab1bea5e7760 100644
--- a/optimization/optimizers/CMakeLists.txt
+++ b/optimization/optimizers/CMakeLists.txt
@@ -1,3 +1,4 @@
+add_subdirectory(cma_es)
 add_subdirectory(conjugate_gradient_descent)
 add_subdirectory(gradient_descent)
 add_subdirectory(nelder_mead)
diff --git a/optimization/optimizers/cma_es/CMakeLists.txt b/optimization/optimizers/cma_es/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..94a71510805d26d469a2244e1c3df98d71dad872
--- /dev/null
+++ b/optimization/optimizers/cma_es/CMakeLists.txt
@@ -0,0 +1,8 @@
+if (VISUALIZE)
+    add_executable(cma_es
+        main.cpp
+    )
+    target_link_libraries(cma_es optimization_lib cma-es clara)
+    #make_plot_target(cma_es 2d ARGS -d 2)
+    #make_plot_target(cma_es 10d ARGS -d 10)
+endif()
diff --git a/optimization/optimizers/cma_es/cma_es.h b/optimization/optimizers/cma_es/cma_es.h
new file mode 100644
index 0000000000000000000000000000000000000000..4ce85d8645b2811c49cf0779470915a0b5d77162
--- /dev/null
+++ b/optimization/optimizers/cma_es/cma_es.h
@@ -0,0 +1,114 @@
+#ifndef OPTIMIZATION_CMA_ES_H
+#define OPTIMIZATION_CMA_ES_H
+
+#include "utils/vector.h"
+#include "cmaes_interface.h"
+#include <iostream>
+#include <iomanip>
+
+namespace optimization {
+
+//--------------------------------------------------------------------------------------------------
+class CmaES {
+public:
+    CmaES() {}
+    CmaES(uint32_t d, uint32_t ps, int32_t s, uint32_t me, uint32_t mi, Scalar ryt) :
+        dim_(d),
+        pop_size_(ps),
+        seed_(s),
+        max_evaulations_(me),
+        max_iterations_(mi),
+        rel_y_tol_(ryt)
+    {}
+
+    uint32_t n_evaluations() const { return n_evaluations_; }
+    uint32_t n_iterations() const { return n_iterations_; }
+
+    template <typename Objective>
+    VectorXs optimize(
+        Objective& objective,
+        VectorXs const& initial_point,
+        VectorXs const& initial_std_dev
+    );
+
+private:
+    uint32_t dim_;
+    uint32_t pop_size_;
+    int32_t seed_;
+    uint32_t max_evaulations_;
+    uint32_t max_iterations_;
+    Scalar rel_y_tol_;
+
+    uint32_t n_evaluations_;
+    uint32_t n_iterations_;
+};
+
+//..................................................................................................
+template <typename Objective>
+VectorXs CmaES::optimize(
+    Objective& objective,
+    VectorXs const& initial_point,
+    VectorXs const& initial_std_dev
+) {
+    n_evaluations_ = 0;
+    n_iterations_ = 0;
+
+    // Decalare the CMA-ES object, and the arrays we will use to communicate with it.
+    cmaes_t cma_es;
+    double* const* points;
+    double* values;
+
+    // For some reason cmaes_init_para takes double*, not double const*, so I have to copy these.
+    // TODO: Looking at Hansen's code, there's no reason they aren't const. I should submit a PR.
+    VectorXs point = initial_point;
+    VectorXs std_dev = initial_std_dev;
+
+    cmaes_init_para(
+        &cma_es,
+        dim_,
+        point.data(),
+        std_dev.data(),
+        seed_,
+        pop_size_,
+        "none"
+    );
+    cma_es.sp.stopMaxFunEvals = max_evaulations_;
+    cma_es.sp.stopMaxIter = max_iterations_;
+    cma_es.sp.stopTolFun = rel_y_tol_;
+    values = cmaes_init_final(&cma_es);
+
+    std::cout << cmaes_SayHello(&cma_es) << '\n';
+
+    while(!cmaes_TestForTermination(&cma_es)) {
+        // Get a generation of points.
+        points = cmaes_SamplePopulation(&cma_es);
+
+        // TODO: This wants to be parallelized.
+        for (uint32_t i = 0; i < pop_size_; ++i) {
+            auto point_map = Eigen::Map<const Eigen::MatrixXd>(points[i], dim_, 1);
+            // TODO: make objectives handle Maps so we don't have to copy the data.
+            point = point_map;
+            objective.eval(point, values[i]);
+        }
+
+        // Update the search distribution used for cmaes_SamplePopulation().
+        cmaes_UpdateDistribution(&cma_es, values);
+        ++n_iterations_;
+    }
+
+    std::cout << cmaes_TestForTermination(&cma_es) << '\n';
+    //cmaes_WriteToFile(&cma_es, "all", "allcmaes.dat");  // write final results
+
+    // Get best estimator for the optimum, xmean.
+    double const* final_point_data = cmaes_GetPtr(&cma_es, "xmean");
+    auto point_map = Eigen::Map<const Eigen::MatrixXd>(final_point_data, dim_, 1);
+    point = point_map;
+    n_evaluations_ = cmaes_Get(&cma_es, "eval");
+    cmaes_exit(&cma_es);
+
+    return point;
+}
+
+}
+
+#endif
diff --git a/optimization/optimizers/cma_es/main.cpp b/optimization/optimizers/cma_es/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e629fa7ae3f6ab9455d575a0f594b3ab55b0e6d5
--- /dev/null
+++ b/optimization/optimizers/cma_es/main.cpp
@@ -0,0 +1,83 @@
+#include "clara.hpp"
+#include "cma_es.h"
+#include "objectives/paraboloid.h"
+#include "objectives/rosenbrock.h"
+#include "utils/eigen_json.h"
+#include <iostream>
+#include <fstream>
+
+using namespace optimization;
+using json = nlohmann::json;
+
+//--------------------------------------------------------------------------------------------------
+int main(int const argc, char const** argv) {
+    std::string log_file_path;
+    std::string vis_file_path;
+    uint32_t dim = 2;
+    uint32_t pop_size = 10;
+    int32_t seed = 0xdefceed9;
+    uint32_t max_evaluations = 1000;
+    uint32_t max_iterations = 1000;
+    Scalar rel_y_tol = 1e-8;
+    Scalar x0 = -1;
+    Scalar y0 = 2;
+    Scalar std_dev = 1;
+
+    auto const cli =
+        clara::Arg(log_file_path, "log_file_path")("Location of the optimization log") |
+        clara::Arg(vis_file_path, "vis_file_path")("Location of the visualization file") |
+        clara::Opt(dim, "dim")["-d"]["--dimension"]("Dimensionality of the objective") |
+        clara::Opt(pop_size, "pop_size")["-p"]["--pop-size"]("Population size") |
+        clara::Opt(seed, "seed")["-s"]["--seed"]("Seed for CMA-ES random number generator") |
+        clara::Opt(max_evaluations, "max_evaluations")["-n"]["--max-evaluations"]("Max number of function evaluations") |
+        clara::Opt(max_iterations, "max_iterations")["-n"]["--max-iterations"]("Max number of generations") |
+        clara::Opt(rel_y_tol, "rel_y_tol")["-t"]["--rel-y-tol"]("Termination condition") |
+        clara::Opt(x0, "x0")["-x"]["--x0"]("X coordinate of initial point") |
+        clara::Opt(y0, "y0")["-y"]["--y0"]("Y coordinate of initial point");
+        clara::Opt(std_dev, "std_dev")["-d"]["--std-dev"]("Initial standard deviation");
+    auto const result = cli.parse(clara::Args(argc, argv));
+    if (!result) {
+        std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
+        exit(1);
+    }
+
+    VectorXs initial_point;
+    initial_point.resize(dim);
+    initial_point[0] = x0;
+    initial_point[1] = y0;
+    for (uint32_t i = 2; i < dim; ++i) {
+        initial_point[i] = -1;
+    }
+
+    VectorXs initial_std_dev;
+    initial_std_dev.resize(dim);
+    initial_std_dev.fill(std_dev);
+
+    //using Objective = Paraboloid<Vector2<Scalar>>;
+    //Objective objective(dim);
+    using Objective = Rosenbrock<-1>;
+    Objective objective;
+    objective.dim() = dim;
+
+    CmaES optimizer(dim, pop_size, seed, max_evaluations, max_iterations, rel_y_tol);
+    VectorXs minimum = optimizer.optimize(objective, initial_point, initial_std_dev);
+    std::cout << "n evaluations: " << optimizer.n_evaluations() << '\n';
+    std::cout << "n generations: " << optimizer.n_iterations() << '\n';
+    std::cout << "final point: " << minimum << '\n';
+
+    /*
+    if (!log_file_path.empty()) {
+        json data = optimizer;
+        std::ofstream log_file(log_file_path);
+        log_file << data.dump(4) << '\n';
+    }
+
+    if (!vis_file_path.empty()) {
+        json data = GradientDescentVis<-1>{optimizer};
+        std::ofstream vis_file(vis_file_path);
+        vis_file << data.dump(4) << '\n';
+    }
+    */
+
+    return 0;
+}