Exemplo n.º 1
0
 def __init__(self,
              memory=10,
              delay=0.001,
              plot_initial=True,
              plot_improvements_only=False,
              ax=None,
              legendloc=1):
     if ax is None:
         self.fig, ax = plt.subplots(1, 1)
     else:
         self.fig = plt.gcf()
     self.hdisplay = display("", display_id=True)
     XYPlotComp.__init__(self, ax=ax)
Exemplo n.º 2
0
def test_TopFarmListRecorder_continue(tf_generator, load_case, n_rec, n_fev):

    D = 80.0
    D2 = 2 * D + 10
    init_pos = np.array([(0, 2 * D), (0, 0), (0, -2 * D)])
    init_pos[:, 0] += [-40, 0, 40]

    pyFuga = test_pyfuga.get_fuga()(init_pos[:, 0],
                                    init_pos[:, 1],
                                    wind_atlas='MyFarm/north_pm45_only.lib')
    boundary = [(-D2, -D2), (D2, D2)]
    plot_comp = XYPlotComp()
    plot_comp = NoPlot()
    tf = TopFarmProblem(
        dict(zip('xy', init_pos.T)),
        cost_comp=pyFuga.get_TopFarm_cost_component(),
        constraints=[
            SpacingConstraint(2 * D),
            XYBoundaryConstraint(boundary, 'square')
        ],
        driver=EasyScipyOptimizeDriver(tol=1e-10, disp=False),
        plot_comp=plot_comp,
        record_id=tfp +
        'recordings/test_TopFarmListRecorder_continue:%s' % load_case,
        expected_cost=25)

    _, _, recorder = tf.optimize()
    # Create test file:
    # 1) delete file "test_files/recordings/test_TopFarmListRecorder_continue"
    # 2) Uncomment line below, run and recomment
    # if load_case=="": recorder.save() # create test file
    npt.assert_equal(recorder.driver_cases.num_cases, n_rec)
    npt.assert_equal(tf.driver.result['nfev'], n_fev)

    tf.plot_comp.show()
Exemplo n.º 3
0
def main():
    if __name__ == '__main__':
        try:
            import matplotlib.pyplot as plt
            plt.gcf()
            plot_comp = XYPlotComp()
            plot = True
        except RuntimeError:
            plot_comp = NoPlot()
            plot = False

        n_wt = 16
        site = IEA37Site(n_wt)
        windTurbines = IEA37_WindTurbines()
        wake_model = IEA37SimpleBastankhahGaussian(site, windTurbines)
        Drotor_vector = [windTurbines.diameter()] * n_wt
        power_rated_vector = [float(windTurbines.power(20) / 1000)] * n_wt
        hub_height_vector = [windTurbines.hub_height()] * n_wt
        AEPCalc = AEPCalculator(wake_model)

        def aep_func(x, y, **kwargs):
            return AEPCalc.calculate_AEP(x_i=x, y_i=y).sum(-1).sum(-1) * 10**6

        def irr_func(aep, **kwargs):
            my_irr = economic_evaluation(Drotor_vector, power_rated_vector,
                                         hub_height_vector,
                                         aep).calculate_irr()
            print(my_irr)
            return my_irr

        aep_comp = CostModelComponent(input_keys=['x', 'y'],
                                      n_wt=n_wt,
                                      cost_function=aep_func,
                                      output_key="aep",
                                      output_unit="GWh",
                                      objective=False,
                                      output_val=np.zeros(n_wt))
        irr_comp = CostModelComponent(input_keys=['aep'],
                                      n_wt=n_wt,
                                      cost_function=irr_func,
                                      output_key="irr",
                                      output_unit="%",
                                      objective=True,
                                      income_model=True)
        group = TopFarmGroup([aep_comp, irr_comp])
        problem = TopFarmProblem(
            design_vars=dict(zip('xy', site.initial_position.T)),
            cost_comp=group,
            driver=EasyRandomSearchDriver(
                randomize_func=RandomizeTurbinePosition_Circle(), max_iter=50),
            constraints=[
                SpacingConstraint(200),
                CircleBoundaryConstraint([0, 0], 1300.1)
            ],
            plot_comp=plot_comp)
        cost, state, recorder = problem.optimize()
Exemplo n.º 4
0
def main():
    if __name__ == '__main__':
        n_wt = 16
        site = IEA37Site(n_wt)
        windTurbines = IEA37_WindTurbines()
        windFarmModel = IEA37SimpleBastankhahGaussian(site, windTurbines)
        tf = TopFarmProblem(
            design_vars=dict(zip('xy', site.initial_position.T)),
            cost_comp=PyWakeAEPCostModelComponent(windFarmModel, n_wt),
            driver=EasyRandomSearchDriver(
                randomize_func=RandomizeTurbinePosition_Circle(), max_iter=5),
            constraints=[CircleBoundaryConstraint([0, 0], 1300.1)],
            plot_comp=XYPlotComp())
        tf.optimize()
        tf.plot_comp.show()
def main():
    if __name__ == '__main__':
        plot_comp = XYPlotComp()
        site = get_site()
        n_wt = len(site.initial_position)
        windTurbines = DTU10MW()
        min_spacing = 2 * windTurbines.diameter(0)
        windFarmModel = IEA37SimpleBastankhahGaussian(site, windTurbines)
        Drotor_vector = [windTurbines.diameter()] * n_wt
        power_rated_vector = [float(windTurbines.power(20) / 1000)] * n_wt
        hub_height_vector = [windTurbines.hub_height()] * n_wt

        def aep_func(x, y, **_):
            sim_res = windFarmModel(x, y)
            aep = sim_res.aep()
            return aep.sum(['wd', 'ws']).values * 10**6

        def irr_func(aep, **_):
            return economic_evaluation(Drotor_vector, power_rated_vector,
                                       hub_height_vector, aep).calculate_irr()

        aep_comp = CostModelComponent(input_keys=['x', 'y'],
                                      n_wt=n_wt,
                                      cost_function=aep_func,
                                      output_key="aep",
                                      output_unit="GWh",
                                      objective=False,
                                      output_val=np.zeros(n_wt))
        irr_comp = CostModelComponent(input_keys=['aep'],
                                      n_wt=n_wt,
                                      cost_function=irr_func,
                                      output_key="irr",
                                      output_unit="%",
                                      objective=True,
                                      income_model=True)
        group = TopFarmGroup([aep_comp, irr_comp])
        problem = TopFarmProblem(
            design_vars=dict(zip('xy', site.initial_position.T)),
            cost_comp=group,
            driver=EasyRandomSearchDriver(
                randomize_func=RandomizeTurbinePosition_Circle(), max_iter=10),
            constraints=[
                SpacingConstraint(min_spacing),
                XYBoundaryConstraint(site.boundary),
            ],
            plot_comp=plot_comp)
        cost, state, recorder = problem.optimize()
        problem.plot_comp.show()
Exemplo n.º 6
0
def main():
    if __name__ == '__main__':
        site = IEA37Site(16)
        windTurbines = IEA37_WindTurbines()
        wake_model = IEA37SimpleBastankhahGaussian(windTurbines)
        aep_calc = PyWakeAEP(site, windTurbines, wake_model)
        tf = TopFarmProblem(
            design_vars=dict(zip('xy', site.initial_position.T)),
            cost_comp=aep_calc.get_TopFarm_cost_component(16),
            driver=EasyRandomSearchDriver(
                randomize_func=RandomizeTurbinePosition_Circle(), max_iter=5),
            constraints=[CircleBoundaryConstraint([0, 0], 1300.1)],
            plot_comp=XYPlotComp())
        # tf.evaluate()
        tf.optimize()
        tf.plot_comp.show()
def main():
    if __name__ == '__main__':
        try:
            import matplotlib.pyplot as plt
            plt.gcf()
            plot_comp = XYPlotComp()
            plot = True
        except RuntimeError:
            plot_comp = NoPlot()
            plot = False

        n_wt = 16
        site = IEA37Site(n_wt)
        windTurbines = IEA37_WindTurbines()
        windFarmModel = IEA37SimpleBastankhahGaussian(site, windTurbines)
        Drotor_vector = [windTurbines.diameter()] * n_wt
        power_rated_vector = [float(windTurbines.power(20)) * 1e-6] * n_wt
        hub_height_vector = [windTurbines.hub_height()] * n_wt
        distance_from_shore = 10         # [km]
        energy_price = 0.1              # [Euro/kWh] What we get per kWh
        project_duration = 20            # [years]
        rated_rpm_array = [12] * n_wt    # [rpm]
        water_depth_array = [15] * n_wt  # [m]

        eco_eval = economic_evaluation(distance_from_shore, energy_price, project_duration)

        def irr_func(aep, **kwargs):
            eco_eval.calculate_irr(
                rated_rpm_array,
                Drotor_vector,
                power_rated_vector,
                hub_height_vector,
                water_depth_array,
                aep)
            print(eco_eval.IRR)
            return eco_eval.IRR

        aep_comp = CostModelComponent(
            input_keys=['x', 'y'],
            n_wt=n_wt,
            cost_function=lambda x, y, **_: windFarmModel(x=x, y=y).aep().sum(['wd', 'ws']) * 10**6,
            output_key="aep",
            output_unit="kWh",
            objective=False,
            output_val=np.zeros(n_wt))
        irr_comp = CostModelComponent(
            input_keys=['aep'],
            n_wt=n_wt,
            cost_function=irr_func,
            output_key="irr",
            output_unit="%",
            objective=True,
            income_model=True)
        group = TopFarmGroup([aep_comp, irr_comp])
        problem = TopFarmProblem(
            design_vars=dict(zip('xy', site.initial_position.T)),
            cost_comp=group,
            driver=EasyRandomSearchDriver(randomize_func=RandomizeTurbinePosition_Circle(), max_iter=5),
            constraints=[SpacingConstraint(200),
                         CircleBoundaryConstraint([0, 0], 1300.1)],
            plot_comp=plot_comp)
        cost, state, recorder = problem.optimize()
Exemplo n.º 8
0
def main():
    if __name__ == '__main__':
        if not 'plantenergy' in topfarm.plugins:
            pass
        else:
            try:

                from plantenergy.GeneralWindFarmGroups import AEPGroup
                from plantenergy.floris import floris_wrapper, add_floris_params_IndepVarComps
                try:
                    import matplotlib.pyplot as plt
                    plt.gcf()
                    plot_comp = XYPlotComp()
                    plot = True
                except RuntimeError:
                    plot_comp = NoPlot()
                    plot = False
    #            plot_comp = NoPlot()

                def setup_prob(differentiable):

                    #####################################
                    ## Setup Floris run with gradients ##
                    #####################################
                    topfarm.x_key = 'turbineX'
                    topfarm.y_key = 'turbineY'
                    turbineX = np.array(
                        [1164.7, 947.2, 1682.4, 1464.9, 1982.6, 2200.1])
                    turbineY = np.array(
                        [1024.7, 1335.3, 1387.2, 1697.8, 2060.3, 1749.7])
                    f = np.array([
                        3.597152, 3.948682, 5.167395, 7.000154, 8.364547,
                        6.43485, 8.643194, 11.77051, 15.15757, 14.73792,
                        10.01205, 5.165975
                    ])
                    wind_speed = 8
                    site = Amalia1Site(f, mean_wsp=wind_speed)
                    site.initial_position = np.array([turbineX, turbineY]).T
                    wt = NREL5MWREF()
                    wake_model = NOJ(site, wt)
                    aep_calculator = AEPCalculator(wake_model)
                    n_wt = len(turbineX)
                    differentiable = differentiable
                    wake_model_options = {
                        'nSamples': 0,
                        'nRotorPoints': 1,
                        'use_ct_curve': True,
                        'ct_curve': ct_curve,
                        'interp_type': 1,
                        'differentiable': differentiable,
                        'use_rotor_components': False
                    }

                    aep_comp = AEPGroup(
                        n_wt,
                        differentiable=differentiable,
                        use_rotor_components=False,
                        wake_model=floris_wrapper,
                        params_IdepVar_func=add_floris_params_IndepVarComps,
                        wake_model_options=wake_model_options,
                        datasize=len(power_curve),
                        nDirections=len(f),
                        cp_points=len(power_curve))  # , cp_curve_spline=None)

                    def cost_func(AEP, **kwargs):
                        return AEP

                    cost_comp = CostModelComponent(input_keys=[('AEP', [0])],
                                                   n_wt=n_wt,
                                                   cost_function=cost_func,
                                                   output_key="aep",
                                                   output_unit="kWh",
                                                   objective=True,
                                                   income_model=True,
                                                   input_units=['kW*h'])
                    group = TopFarmGroup([aep_comp, cost_comp])
                    boundary = np.array([(900, 1000), (2300, 1000),
                                         (2300, 2100),
                                         (900, 2100)])  # turbine boundaries
                    prob = TopFarmProblem(
                        design_vars={
                            'turbineX': (turbineX, 'm'),
                            'turbineY': (turbineY, 'm')
                        },
                        cost_comp=group,
                        driver=EasyRandomSearchDriver(
                            randomize_func=RandomizeTurbinePosition_Square(),
                            max_iter=500),
                        #                        driver=EasyScipyOptimizeDriver(optimizer='SLSQP',tol=10**-12),
                        #                        driver=EasyScipyOptimizeDriver(optimizer='COBYLA'),
                        constraints=[
                            SpacingConstraint(200, units='m'),
                            XYBoundaryConstraint(boundary, units='m')
                        ],
                        plot_comp=plot_comp,
                        expected_cost=-100e2,
                    )
                    turbineZ = np.array([90.0, 100.0, 90.0, 80.0, 70.0, 90.0])
                    air_density = 1.1716  # kg/m^3
                    rotorDiameter = np.zeros(n_wt)
                    hubHeight = np.zeros(n_wt)
                    axialInduction = np.zeros(n_wt)
                    generatorEfficiency = np.zeros(n_wt)
                    yaw = np.zeros(n_wt)
                    for turbI in range(0, n_wt):
                        rotorDiameter[turbI] = wt.diameter()  # m
                        hubHeight[turbI] = wt.hub_height()  # m
                        axialInduction[turbI] = 1.0 / 3.0
                        generatorEfficiency[turbI] = 1.0  # 0.944
                        yaw[turbI] = 0.  # deg.
                    prob['turbineX'] = turbineX
                    prob['turbineY'] = turbineY
                    prob['hubHeight'] = turbineZ
                    prob['yaw0'] = yaw
                    prob['rotorDiameter'] = rotorDiameter
                    prob['hubHeight'] = hubHeight
                    prob['axialInduction'] = axialInduction
                    prob['generatorEfficiency'] = generatorEfficiency
                    prob['windSpeeds'] = np.ones(len(f)) * wind_speed
                    prob['air_density'] = air_density

                    prob['windDirections'] = np.arange(0, 360, 360 / len(f))
                    prob['windFrequencies'] = f / 100
                    # turns off cosine spread (just needs to be very large)
                    prob['model_params:cos_spread'] = 1E12
                    prob['model_params:shearExp'] = 0.25
                    prob['model_params:z_ref'] = 80.
                    prob['model_params:z0'] = 0.
                    prob['rated_power'] = np.ones(n_wt) * 5000.
                    prob['cut_in_speed'] = np.ones(n_wt) * 3
                    prob['cp_curve_wind_speed'] = cp_curve[:, 0]
                    prob['cp_curve_cp'] = cp_curve[:, 1]
                    prob['rated_wind_speed'] = np.ones(n_wt) * 11.4
                    prob['cut_out_speed'] = np.ones(n_wt) * 25.0
                    # if 0:
                    # prob.check_partials(compact_print=True,includes='*direction_group0*')
                    # else:
                    return prob

                differentiable = True
                prob = setup_prob(differentiable)
                #            view_model(prob)
                cost_init, state_init = prob.evaluate()
                tic = time.time()
                cost, state, recorder = prob.optimize()
                toc = time.time()
                print(
                    'FLORIS calculation with differentiable = {0} took {1} sec.'
                    .format(differentiable, toc - tic))
                print(prob[topfarm.x_key])

    #            ########################################
    #            ## Setup Floris run without gradients ##
    #            ########################################
    #
    #            differentiable = False
    #            prob2 = setup_prob(differentiable)
    #            cost_init, state_init = prob2.evaluate()
    #            tic = time.time()
    #            cost, state, recorder = prob2.optimize()
    #            toc = time.time()
    #            print('FLORIS calculation with differentiable = {0} took {1} sec.'.format(differentiable, toc-tic))
    #
    #
    #
    #            ########################################
    #            ## Setup Pywake run without gradients ##
    #            ########################################
    #
    #
    #
    #            class PyWakeAEP(AEPCalculator):
    #                """TOPFARM wrapper for PyWake AEP calculator"""
    #
    #                def get_TopFarm_cost_component(self, n_wt, wd=None, ws=None):
    #                    """Create topfarm-style cost component
    #
    #                    Parameters
    #                    ----------
    #                    n_wt : int
    #                        Number of wind turbines
    #                    """
    #                    return AEPCostModelComponent(
    #                        input_keys=['turbineX', 'turbineY'],
    #                        n_wt=n_wt,
    #                        cost_function=lambda **kwargs:
    #                            self.calculate_AEP(x_i=kwargs[topfarm.x_key],
    #                                               y_i=kwargs[topfarm.y_key],
    #                                               h_i=kwargs.get(topfarm.z_key, None),
    #                                               type_i=kwargs.get(topfarm.type_key, None),
    #                                               wd=wd, ws=ws).sum(),
    #                        output_unit='GWh')
    #            aep_calc = PyWakeAEP(site, wt, wake_model)
    #            tf = TopFarmProblem(
    #                    design_vars={'turbineX': turbineX, 'turbineY': turbineY},
    #                cost_comp=aep_calc.get_TopFarm_cost_component(len(turbineX)),
    #                    driver=EasyScipyOptimizeDriver(optimizer='SLSQP'),
    #                    constraints=[SpacingConstraint(200),
    #                                 XYBoundaryConstraint(boundary)],
    #                    plot_comp=plot_comp)
    #            cost_init_pw, state_init_pw = tf.evaluate()
    #            cost_pw, state_pw, recorder_pw = tf.optimize()
    #
    #
    #            prob['turbineX'] = state_pw['turbineX']
    #            prob['turbineY'] = state_pw['turbineY']
    #            cost_pw_fl, state_pw_fl = prob.evaluate()
    #            print('\n***Optimized by PyWake:***')
    #            print('AEP FLORISSE initial:    ', float(-cost_init/10**6))
    #            print('AEP FLORISSE optimized:  ', float(-cost_pw_fl)/10**6)
    #            print('AEP PyWake initial:    ', float(-cost_init_pw))
    #            print('AEP PyWake optimized:  ', float(-cost_pw))
    #
    #            print('***Optimized by FLORISSE:***')
    #            print('AEP FLORISSE initial:    ', float(-cost_init/10**6))
    #            print('AEP FLORISSE optimized:  ', float(-cost)/10**6)
    #            print('AEP Pywake initial:     ', aep_calculator.calculate_AEP(turbineX, turbineY).sum())
    #            print('AEP Pywake optimized:   ', aep_calculator.calculate_AEP(state['turbineX'], state['turbineY']).sum())
            finally:
                topfarm.x_key = 'x'
                topfarm.y_key = 'y'
Exemplo n.º 9
0
 def compute(self, inputs, outputs):
     XYPlotComp.compute(self, inputs, outputs)
     self.hdisplay.update(self.fig)
Exemplo n.º 10
0
 def plot_current_position(self, x, y):
     elnet_layout = mst(x, y)
     indices = np.array(list(elnet_layout.keys())).T
     self.ax.plot(x[indices], y[indices], color='r')
     XYPlotComp.plot_current_position(self, x, y)
Exemplo n.º 11
0
    design_vars={
        'x': x_init,
        'y': y_init
    },
    constraints=[
        XYBoundaryConstraint(boundary),
        SpacingConstraint(min_spacing)
    ],
    post_constraints=[('water_depth', {
        'lower': np.ones(n_wt) * maximum_water_depth
    })],
    cost_comp=cost_comp,
    driver=EasyScipyOptimizeDriver(optimizer='SLSQP', maxiter=maxiter,
                                   tol=tol),
    # driver=EasyRandomSearchDriver(RandomizeTurbinePosition()),
    plot_comp=XYPlotComp(),
    expected_cost=ec)

if 1:
    tic = time.time()
    cost, state, recorder = problem.optimize()
    toc = time.time()
    print('Optimization took: {:.0f}s'.format(toc - tic))

    plt.figure()
    plt.plot(recorder['water_depth'].min((1)))
    plt.plot([0, recorder['water_depth'].shape[0]],
             [maximum_water_depth, maximum_water_depth])
    plt.xlabel('Iteration')
    plt.ylabel('Max depth [m]')
    plt.show()
Exemplo n.º 12
0
def main():
    if __name__ == '__main__':
        try:
            import matplotlib.pyplot as plt
            plt.gcf()
            plot_comp = XYPlotComp()
            plot = True
        except RuntimeError:
            plot_comp = NoPlot()
            plot = False
        # ------------------------ INPUTS ------------------------

        # paths to input files
        test_files_dir = os.path.dirname(
            test_files.__file__) + "/"  # file locations
        wf_path = test_files_dir + 'wind_farms/3tb.yml'  # path to wind farm

        # ------------------------ DEFINE WIND RESOURCE ------------------------
        # wind resource info (wdir frequency, weibull a and k)
        f = [
            3.597152, 3.948682, 5.167395, 7.000154, 8.364547, 6.43485,
            8.643194, 11.77051, 15.15757, 14.73792, 10.01205, 5.165975
        ]
        a = [
            9.176929, 9.782334, 9.531809, 9.909545, 10.04269, 9.593921,
            9.584007, 10.51499, 11.39895, 11.68746, 11.63732, 10.08803
        ]
        k = [
            2.392578, 2.447266, 2.412109, 2.591797, 2.755859, 2.595703,
            2.583984, 2.548828, 2.470703, 2.607422, 2.626953, 2.326172
        ]

        wind_res = WindResource(f, a, k, np.zeros_like(k))

        # ------------------------ setup problem ____---------------------------
        rot_diam = 80.0  # rotor diameter [m]
        init_pos = np.array([(0, 2 * rot_diam), (0, 0),
                             (0, -2 * rot_diam)])  # initial turbine positions
        b = 2 * rot_diam + 10  # boundary size
        boundary = [(-b, -b), (-b, b), (b, b),
                    (b, -b)]  # corners of wind farm boundary
        min_spacing = 2.0 * rot_diam  # minimum spacing between turbines [m]

        # ------------------------ OPTIMIZATION ------------------------

        def get_tf(wake_model):
            return TopFarmProblem(design_vars=dict(zip('xy', init_pos.T)),
                                  cost_comp=AEPCalculator(
                                      wind_res,
                                      wake_model,
                                      wdir=np.arange(
                                          0, 360,
                                          12)).get_TopFarm_cost_component(),
                                  constraints=[
                                      SpacingConstraint(min_spacing),
                                      XYBoundaryConstraint(boundary)
                                  ],
                                  driver=EasyScipyOptimizeDriver(),
                                  plot_comp=plot_comp)

        with warnings.catch_warnings():
            warnings.filterwarnings(
                'ignore')  # temporarily disable fusedwake warnings

            # GCL: define the wake model and optimization problem
            wake_mod_gcl = FusedWakeGCLWakeModel(wf_path)
            tf_gcl = get_tf(wake_mod_gcl)

            # NOJ: define the wake model and optimization problem
            wake_mod_noj = FusedWakeNOJWakeModel(wf_path)
            tf_noj = get_tf(wake_mod_noj)

            # run the optimization
            cost_gcl, state_gcl, recorder_gcl = tf_gcl.optimize()
            cost_noj, state_noj, recorder_noj = tf_noj.optimize()

            # ------------------------ POST-PROCESS ------------------------

            # get the optimized locations
            opt_gcl = tf_gcl.turbine_positions
            opt_noj = tf_noj.turbine_positions

            # create the array of costs for easier printing
            costs = np.diag([cost_gcl, cost_noj])
            costs[0, 1] = tf_noj.evaluate(state_gcl)[0]  # noj cost of gcl locs
            costs[1, 0] = tf_gcl.evaluate(state_noj)[0]  # gcl cost of noj locs

        # ------------------------ PRINT STATS ------------------------

        aep_diffs = 200 * (costs[:, 0] - costs[:, 1]) / (costs[:, 0] +
                                                         costs[:, 1])
        loc_diffs = 200 * (costs[0, :] - costs[1, :]) / (costs[0, :] +
                                                         costs[1, :])

        print('\nComparison of cost models vs. optimized locations:')
        print('\nCost    |    GCL_aep      NOJ_aep')
        print('---------------------------------')
        print(f'GCL_loc |{costs[0,0]:11.2f} {costs[0,1]:11.2f}' +
              f'   ({aep_diffs[0]:.2f}%)')
        print(f'NOJ_loc |{costs[1,0]:11.2f} {costs[1,1]:11.2f}' +
              f'   ({aep_diffs[1]:.2f}%)')
        print(f'             ({loc_diffs[0]:.2f}%)     ({loc_diffs[1]:.2f}%)')

        # ------------------------ PLOT (if possible) ------------------------

        if plot:

            # initialize the figure and axes
            fig = plt.figure(1, figsize=(7, 5))
            plt.clf()
            ax = plt.axes()

            # plot the boundary and desired locations
            ax.add_patch(Polygon(boundary, fill=False,
                                 label='Boundary'))  # boundary
            ax.plot(init_pos[:, 0], init_pos[:, 1], 'xk', label='Initial')
            ax.plot(opt_gcl[:, 0], opt_gcl[:, 1], 'o', label='GCL')
            ax.plot(opt_noj[:, 0], opt_noj[:, 1], '^', label='NOJ')

            # make a few adjustments to the plot
            ax.autoscale_view()  # autoscale the boundary
            plt.legend(bbox_to_anchor=(0., 1.02, 1., .102),
                       loc=3,
                       ncol=4,
                       mode='expand',
                       borderaxespad=0.)  # add a legend
            plt.tight_layout()  # zoom the plot in
            plt.axis('off')  # remove the axis

            # save the png
            folder, file = os.path.split(__file__)
            fig.savefig(folder + "/figures/" + file.replace('.py', '.png'))