From f6b1b22b0d797ae424ce645e1c9561c08f6c1d2e Mon Sep 17 00:00:00 2001
From: "Mads M. Pedersen" <mmpe@dtu.dk>
Date: Thu, 14 Jun 2018 10:22:29 +0200
Subject: [PATCH] implemented easy_drivers.py

---
 tests/topfarm/__init__.py           |  0
 tests/topfarm/test_drivers.py       | 73 ++++++++++++++++++++++++
 tests/{ => topfarm}/test_topfarm.py |  0
 topfarm/_topfarm.py                 | 17 +++---
 topfarm/cost_models/dummy.py        |  3 -
 topfarm/easy_drivers.py             | 88 +++++++++++++++++++++++++++++
 6 files changed, 169 insertions(+), 12 deletions(-)
 create mode 100644 tests/topfarm/__init__.py
 create mode 100644 tests/topfarm/test_drivers.py
 rename tests/{ => topfarm}/test_topfarm.py (100%)
 create mode 100644 topfarm/easy_drivers.py

diff --git a/tests/topfarm/__init__.py b/tests/topfarm/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/topfarm/test_drivers.py b/tests/topfarm/test_drivers.py
new file mode 100644
index 00000000..718db758
--- /dev/null
+++ b/tests/topfarm/test_drivers.py
@@ -0,0 +1,73 @@
+'''
+Created on 17. maj 2018
+
+@author: mmpe
+'''
+from topfarm import TopFarm
+
+import numpy as np
+import pytest
+from topfarm.cost_models.dummy import DummyCost, DummyCostPlotComp
+from topfarm.plotting import NoPlot
+from topfarm.easy_drivers import EasyScipyOptimizeDriver, EasySimpleGADriver
+
+
+initial = [[6, 0], [6, -8], [1, 1]]  # initial turbine layouts
+optimal = np.array([[2.5, -3], [6, -7], [4.5, -3]])  # desired turbine layouts
+boundary = [(0, 0), (6, 0), (6, -10), (0, -10)]  # turbine boundaries
+desired = [[3, -3], [7, -7], [4, -3]]  # desired turbine layouts
+
+
+@pytest.fixture
+def topfarm_generator():
+    def _topfarm_obj(driver):
+        plot_comp = DummyCostPlotComp(desired)
+        #plot_comp = NoPlot()
+        return TopFarm(initial, DummyCost(desired), 2, plot_comp=plot_comp, boundary=boundary, driver=driver)
+    return _topfarm_obj
+
+
+
+
+#         # CONMIN-specific Settings
+#         self.driver.itmax = 30
+#         self.driver.fdch = 0.00001
+#         self.driver.fdchm = 0.000001
+#         self.driver.ctlmin = 0.01
+#         self.driver.delfun = 0.001
+# 
+#         # NEWSUMT-specific Settings
+#         #self.driver.itmax = 10
+# 
+#         # COBYLA-specific Settings
+#         #self.driver.rhobeg = 1.0
+#         #self.driver.rhoend = 1.0e-4
+#         #self.driver.maxfun = 1000
+# 
+#         # SLSQP-specific Settings
+#         #self.driver.accuracy = 1.0e-6
+#         #self.driver.maxiter = 50
+# 
+#         # Genetic-specific Settings
+#         #self.driver.population_size = 90
+#         #self.driver.crossover_rate = 0.9
+#         #self.driver.mutation_rate = 0.02
+#         #self.selection_method = 'rank'
+
+
+@pytest.mark.parametrize('driver,tol',[(EasyScipyOptimizeDriver(), 1e-4),
+                                       (EasyScipyOptimizeDriver(tol=1e-3), 1e-2),
+                                       (EasyScipyOptimizeDriver(maxiter=13), 1e-1),
+                                       (EasyScipyOptimizeDriver(optimizer='COBYLA', tol=1e-3), 1e-2),
+                                       #(EasyPyOptSparseSLSQP(),??),
+                                       (EasySimpleGADriver(), 1e-4)][-1:])
+def test_optimizers(driver, tol, topfarm_generator):
+    tf = topfarm_generator(driver)
+    tf.optimize()
+    tb_pos = tf.turbine_positions
+    #tf.plot_comp.show()
+
+    assert sum((tb_pos[2] - tb_pos[0])**2) > 2**2 - tol  # check min spacing
+    assert tb_pos[1][0]< 6 + tol  # check within border
+    np.testing.assert_array_almost_equal(tb_pos, optimal, -int(np.log10(tol)))
+    #print (tb_pos - optimal)
diff --git a/tests/test_topfarm.py b/tests/topfarm/test_topfarm.py
similarity index 100%
rename from tests/test_topfarm.py
rename to tests/topfarm/test_topfarm.py
diff --git a/topfarm/_topfarm.py b/topfarm/_topfarm.py
index 572c9efa..c145ea4a 100644
--- a/topfarm/_topfarm.py
+++ b/topfarm/_topfarm.py
@@ -19,7 +19,7 @@ class TopFarm(object):
     """
 
     def __init__(self, turbines, cost_comp, min_spacing, boundary, boundary_type='convex_hull', plot_comp=None,
-                 driver_options={'optimizer': 'SLSQP'}):
+                 driver=ScipyOptimizeDriver()):
 
         self.initial_positions = turbines = np.array(turbines)
 
@@ -32,19 +32,18 @@ class TopFarm(object):
         indeps = prob.model.add_subsystem('indeps', IndepVarComp(), promotes=['*'])
         min_x, min_y = self.boundary_comp.vertices.min(0)
         mean_x, mean_y = self.boundary_comp.vertices.mean(0)
-        if driver_options['optimizer'] == 'SLSQP':
+        design_var_kwargs = {}
+
+        if 'optimizer' in driver.options and driver.options['optimizer'] == 'SLSQP':
             min_x, min_y, mean_x, mean_y = 0, 0, 1, 1  # scaling disturbs SLSQP
+            # Default +/- sys.float_info.max does not work for SLSQP
+            design_var_kwargs = {'lower': np.nan, 'upper': np.nan}
         indeps.add_output('turbineX', turbines[:, 0], units='m', ref0=min_x, ref=mean_x)
         indeps.add_output('turbineY', turbines[:, 1], units='m', ref0=min_y, ref=mean_y)
         indeps.add_output('boundary', self.boundary_comp.vertices, units='m')
         prob.model.add_subsystem('cost_comp', cost_comp, promotes=['*'])
-        prob.driver = ScipyOptimizeDriver()
-
-        prob.driver.options.update(driver_options)
-        design_var_kwargs = {}
-        if driver_options['optimizer'] == 'SLSQP':
-            # Default +/- sys.float_info.max does not work for SLSQP
-            design_var_kwargs = {'lower': np.nan, 'upper': np.nan}
+        prob.driver = driver
+        
         prob.model.add_design_var('turbineX', **design_var_kwargs)
         prob.model.add_design_var('turbineY', **design_var_kwargs)
         prob.model.add_objective('cost')
diff --git a/topfarm/cost_models/dummy.py b/topfarm/cost_models/dummy.py
index 777b77f8..77a99f00 100644
--- a/topfarm/cost_models/dummy.py
+++ b/topfarm/cost_models/dummy.py
@@ -19,9 +19,6 @@ class DummyCost(ExplicitComponent):
         self.optimal = np.array(optimal_positions)
         self.N = self.optimal.shape[0]
 
-    def cost(self, x, y):
-        """Evaluate cost function"""
-
     def setup(self):
         self.add_input('turbineX', val=np.zeros(self.N), units='m')
         self.add_input('turbineY', val=np.zeros(self.N), units='m')
diff --git a/topfarm/easy_drivers.py b/topfarm/easy_drivers.py
new file mode 100644
index 00000000..3f63aa71
--- /dev/null
+++ b/topfarm/easy_drivers.py
@@ -0,0 +1,88 @@
+from openmdao.drivers.scipy_optimizer import ScipyOptimizeDriver
+#from openmdao.drivers.pyoptsparse_driver import pyOptSparseDriver
+from openmdao.drivers.genetic_algorithm_driver import SimpleGADriver
+
+
+class EasyScipyOptimizeDriver(ScipyOptimizeDriver):
+
+    def __init__(self, optimizer='SLSQP', maxiter=200, tol=1e-6, disp=True):
+        """        
+        Parameters
+        ----------
+        optimizer : {'Nelder-Mead', 'Powell', 'CG', 'BFGS', 'Newton-CG', 'L-BFGS-B',
+               'TNC', 'COBYLA', 'SLSQP'}            
+            Inequality constraints are supported by COBYLA and SLSQP,
+            but equality constraints are only supported by SLSQP. None of the other
+            optimizers support constraints.
+        maxiter : int
+            Maximum number of iterations.
+        tol : float
+            Tolerance for termination. For detailed control, use solver-specific options.
+        disp : bool
+            Set to False to prevent printing of Scipy convergence messages
+        """
+        ScipyOptimizeDriver.__init__(self)
+        self.options.update({'optimizer': optimizer, 'maxiter': maxiter, 'tol': tol, 'disp': disp})
+
+
+# class EasyPyOptSparseSLSQP(pyOptSparseDriver):
+#     def __init__(self, maxit=200, acc=1e-6):
+#         pyOptSparseDriver.__init__(self)
+#         raise NotImplementedError
+
+
+class EasySimpleGADriver(SimpleGADriver):
+    def __init__(self, elitism=True, max_gen=100):
+        """Simple Genetic Algorithm Driver with argument
+
+        Parameters
+        ----------
+        bits : dict
+            Number of bits of resolution. Default is an empty dict, where every unspecified variable is assumed to be integer, and the number of bits is calculated automatically. If you have a continuous var, you should set a bits value as a key in this dictionary.
+            NotImplemented
+        debug_print : list
+            List of what type of Driver variables to print at each iteration. Valid items in list are ‘desvars’, ‘ln_cons’, ‘nl_cons’, ‘objs’, ‘totals’
+            NotImplemented
+        elitism : bool
+            If True, replace worst performing point with best from previous generation each iteration.
+        max_gen : int
+            Number of generations before termination.
+        pop_size : 
+            Number of points in the GA. Set to 0 and it will be computed as four times the number of bits.
+            NotImplemented
+        procs_per_model : int
+            Number of processors to give each model under MPI.
+            NotImplemented
+        run_parallel : bool
+            Set to True to execute the points in a generation in parallel.
+            NotImplemented    
+        """
+        SimpleGADriver.__init__(self)
+        self.options.update({'elitism': elitism, 'max_gen': max_gen})
+
+#
+# class COBYLADriverWrapper(CONMINdriver):
+#         # CONMIN-specific Settings
+#         self.driver.itmax = 30
+#         self.driver.fdch = 0.00001
+#         self.driver.fdchm = 0.000001
+#         self.driver.ctlmin = 0.01
+#         self.driver.delfun = 0.001
+#
+#         # NEWSUMT-specific Settings
+#         #self.driver.itmax = 10
+#
+#         # COBYLA-specific Settings
+#         #self.driver.rhobeg = 1.0
+#         #self.driver.rhoend = 1.0e-4
+#         #self.driver.maxfun = 1000
+#
+#         # SLSQP-specific Settings
+#         #self.driver.accuracy = 1.0e-6
+#         #self.driver.maxiter = 50
+#
+#         # Genetic-specific Settings
+#         #self.driver.population_size = 90
+#         #self.driver.crossover_rate = 0.9
+#         #self.driver.mutation_rate = 0.02
+#         #self.selection_method = 'rank'
-- 
GitLab