diff --git a/tests/test_fuga/test_pyfuga.py b/tests/test_fuga/test_pyfuga.py index 9c60ee4e4120fafa15ee86e5c30b59e8827d9e25..3fea940944364ce149b672cfc7b5005ea7395090 100644 --- a/tests/test_fuga/test_pyfuga.py +++ b/tests/test_fuga/test_pyfuga.py @@ -1,17 +1,9 @@ -''' -Created on 16. apr. 2018 - -@author: mmpe -''' -from threading import Thread import threading import time -import unittest - import numpy as np import pytest from topfarm.cost_models.fuga.pascal_dll import PascalDLL -from topfarm.cost_models.fuga.py_fuga import PyFuga +from topfarm.cost_models.fuga.py_fuga import PyFuga, fugalib_path import os from topfarm.cost_models.fuga import py_fuga from topfarm import TopFarm @@ -20,31 +12,15 @@ from topfarm import TopFarm fuga_path = os.path.abspath(os.path.dirname(py_fuga.__file__)) + '/Colonel/' -def _test_parallel(id): - pyFuga = PyFuga() - pyFuga.setup(farm_name='Horns Rev 1', - turbine_model_path=fuga_path + 'LUT/', turbine_model_name='Vestas_V80_(2_MW_offshore)[h=67.00]', - tb_x=[423974, 424033], tb_y=[6151447, 6150889], - mast_position=(0, 0, 70), z0=0.0001, zi=400, zeta0=0, - farms_dir=fuga_path + 'LUT/Farms/', wind_atlas_path='Horns Rev 1\hornsrev0.lib') - print(pyFuga.stdout_filename, id) - for i in range(1): - print(threading.current_thread(), id) - np.testing.assert_array_almost_equal(pyFuga.get_aep([0, 0], [0, 200]), [14.044704, 16.753474, 0.401041, 0.838316]) - time.sleep(1) - - -class Test(unittest.TestCase): - - def lib_missing(self): - lib_path = os.path.dirname(py_fuga.__file__) + "/Colonel/FugaLib/FugaLib.%s" % ('so', 'dll')[os.name == 'nt'] +def check_lib_exists(): + if os.path.isfile(fugalib_path) is False: + pytest.xfail("Fugalib '%s' not found\n" % fugalib_path) - if os.path.isfile(lib_path) is False: - pytest.xfail("Fugalib missing") - raise Warning("Fugalib '%s' not found\n" % lib_path) - return False - def get_fuga(self, tb_x=[423974, 424033], tb_y=[6151447, 6150889]): +@pytest.fixture +def get_fuga(): + def _fuga(tb_x=[423974, 424033], tb_y=[6151447, 6150889]): + check_lib_exists() pyFuga = PyFuga() pyFuga.setup(farm_name='Horns Rev 1', turbine_model_path=fuga_path + 'LUT/', turbine_model_name='Vestas_V80_(2_MW_offshore)[h=67.00]', @@ -52,74 +28,87 @@ class Test(unittest.TestCase): mast_position=(0, 0, 70), z0=0.0001, zi=400, zeta0=0, farms_dir=fuga_path + 'LUT/Farms/', wind_atlas_path='Horns Rev 1/hornsrev_north_only.lib', climate_interpolation=False) return pyFuga + return _fuga - def testCheckVersion(self): - if self.lib_missing(): - return - lib = PascalDLL(fuga_path + "FugaLib/FugaLib.%s" % ('so', 'dll')[os.name == 'nt']) - self.assertRaisesRegex(Exception, "This version of FugaLib supports interface version ", lib.CheckInterfaceVersion, 1) - pyFuga = self.get_fuga() # check that current interface version match - pyFuga.cleanup() - - def testSetup(self): - if self.lib_missing(): - return - pyFuga = self.get_fuga() - self.assertEqual(pyFuga.get_no_tubines(), 2) - self.assertIn("Loading", pyFuga.log) - - # check that new setup resets number of turbines - pyFuga = self.get_fuga() - self.assertEqual(pyFuga.get_no_tubines(), 2) - pyFuga.cleanup() - - def testAEP_one_tb(self): - if self.lib_missing(): - return - pyFuga = self.get_fuga([0], [0]) - np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0], [0]]).T), [7.450272, 7.450272, 0.424962, 1.]) - pyFuga.cleanup() - - def testAEP(self): - if self.lib_missing(): - return - pyFuga = self.get_fuga() - - np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0, 200], [0, 0]]).T), [14.866138, 14.900544, 0.423981, 0.997691]) - np.testing.assert_array_almost_equal(pyFuga.get_aep_gradients(np.array([[0, 200], [0, 0]]).T), 0) + +@pytest.fixture +def pyFuga(): + return get_fuga()() + + +def _test_parallel(i): + pyFuga = get_fuga()() + print(pyFuga.stdout_filename, i) + for _ in range(1): + print(threading.current_thread(), i) np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0, 0], [0, 200]]).T), [12.124883, 14.900544, 0.3458, 0.813721]) - np.testing.assert_array_almost_equal(pyFuga.get_aep_gradients(np.array([[0, 0], [0, 200]]).T), [[-0.001794, 0.001794], - [-0.008126, 0.008126], - [0., 0.]]) - np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0, 200], [0, 200]]).T), [14.864909, 14.900544, 0.423946, 0.997608]) - np.testing.assert_array_almost_equal(pyFuga.get_aep_gradients(np.array([[0, 200], [0, 200]]).T), [[-5.165553e-06, 5.165553e-06], - [1.599768e-06, -1.599768e-06], - [0.000000e+00, 0.000000e+00]]) - pyFuga.cleanup() - - def testAEP_topfarm(self): - if self.lib_missing(): - return - pyFuga = self.get_fuga() - init_pos = [[0, 0], [200, 0]] - tf = TopFarm(init_pos, pyFuga.get_TopFarm_cost_component(), 160, init_pos, boundary_type='square') - tf.evaluate() - np.testing.assert_array_almost_equal(tf.get_cost(), -14.866138) - - def test_pyfuga_cmd(self): - if self.lib_missing(): - return - pyFuga = PyFuga() - pyFuga.execute(r'echo "ColonelInit"') - self.assertEqual(pyFuga.log.strip().split("\n")[-1], 'ColonelInit') + time.sleep(1) + +def testCheckVersion(get_fuga): + check_lib_exists() + lib = PascalDLL(fuga_path + "FugaLib/FugaLib.%s" % ('so', 'dll')[os.name == 'nt']) + with pytest.raises(Exception, match="This version of FugaLib supports interface version "): + lib.CheckInterfaceVersion(1) + pyFuga = get_fuga() # check that current interface version match + pyFuga.cleanup() -# def test_parallel(self): -# from multiprocessing import Pool -# -# with Pool(5) as p: -# print(p.map(_test_parallel, [1, 2])) + +def testSetup(get_fuga): + pyFuga = get_fuga() + assert pyFuga.get_no_turbines() == 2 + assert "Loading" in pyFuga.log + + # check that new setup resets number of turbines + pyFuga = get_fuga() + assert pyFuga.get_no_turbines() == 2 + pyFuga.cleanup() + + +def testAEP_one_tb(get_fuga): + pyFuga = get_fuga([0], [0]) + np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0], [0]]).T), [7.450272, 7.450272, 0.424962, 1.]) + pyFuga.cleanup() + + +def testAEP(pyFuga): + np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0, 200], [0, 0]]).T), [14.866138, 14.900544, 0.423981, 0.997691]) + np.testing.assert_array_almost_equal(pyFuga.get_aep_gradients(np.array([[0, 200], [0, 0]]).T), 0) + np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0, 0], [0, 200]]).T), [12.124883, 14.900544, 0.3458, 0.813721]) + np.testing.assert_array_almost_equal(pyFuga.get_aep_gradients(np.array([[0, 0], [0, 200]]).T), [[-0.001794, 0.001794], + [-0.008126, 0.008126], + [0., 0.]]) + np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0, 200], [0, 200]]).T), [14.864909, 14.900544, 0.423946, 0.997608]) + np.testing.assert_array_almost_equal(pyFuga.get_aep_gradients(np.array([[0, 200], [0, 200]]).T), [[-5.165553e-06, 5.165553e-06], + [1.599768e-06, -1.599768e-06], + [0.000000e+00, 0.000000e+00]]) + pyFuga.cleanup() + + +def testLargeOffset(pyFuga): + o = 1.e16 + np.testing.assert_array_almost_equal(pyFuga.get_aep(np.array([[0 + o, 0 + o], [0 + o, 200 + o]]).T), [12.124883, 14.900544, 0.3458, 0.813721]) + np.testing.assert_array_almost_equal(pyFuga.get_aep_gradients(), [[-0.001794, 0.001794], + [-0.008126, 0.008126], + [0., 0.]]) + +def testAEP_topfarm(get_fuga): + pyFuga = get_fuga() + init_pos = [[0, 0], [200, 0]] + tf = TopFarm(init_pos, pyFuga.get_TopFarm_cost_component(), 160, init_pos, boundary_type='square') + tf.evaluate() + np.testing.assert_array_almost_equal(tf.get_cost(), -14.866138) + + +def test_pyfuga_cmd(): + check_lib_exists() + pyFuga = PyFuga() + pyFuga.execute(r'echo "ColonelInit"') + assert pyFuga.log.strip().split("\n")[-1] == 'ColonelInit' -if __name__ == "__main__": - unittest.main() +# @pytest.mark.xfail +# def test_parallel(): +# from multiprocessing import Pool +# with Pool(5) as p: +# print(p.map(_test_parallel, [1, 2])) diff --git a/topfarm/cost_models/fuga/py_fuga.py b/topfarm/cost_models/fuga/py_fuga.py index cd8816439cee4ed1090594e6ac84b8e1c356614c..a82e5036fea6fe8179bcfd5942c2b8c803363202 100644 --- a/topfarm/cost_models/fuga/py_fuga.py +++ b/topfarm/cost_models/fuga/py_fuga.py @@ -19,6 +19,7 @@ c_double_p = POINTER(c_double) c_int_p = POINTER(ctypes.c_int32) fuga_path = os.path.abspath(os.path.dirname(__file__)) + '/Colonel/' +fugalib_path = os.path.dirname(__file__) + "/Colonel/FugaLib/FugaLib.%s" % ('so', 'dll')[os.name == 'nt'] class PyFuga(object): @@ -29,11 +30,10 @@ class PyFuga(object): with NamedTemporaryFile() as f: self.stdout_filename = f.name + "pyfuga.txt" - lib_path = os.path.dirname(__file__) + "/Colonel/FugaLib/FugaLib.%s" % ('so', 'dll')[os.name == 'nt'] - if os.path.isfile(lib_path) is False: - raise Exception("Fuga lib cannot be found: '%s'" % lib_path) + if os.path.isfile(fugalib_path) is False: + raise Exception("Fuga lib cannot be found: '%s'" % fugalib_path) - self.lib = PascalDLL(lib_path) + self.lib = PascalDLL(fugalib_path) self.lib.CheckInterfaceVersion(self.interface_version) self.lib.Initialize(self.stdout_filename) @@ -45,9 +45,8 @@ class PyFuga(object): self.lib.Setup(float(mast_position[0]), float(mast_position[1]), float(mast_position[2]), float(z0), float(zi), float(zeta0)) - tb_x_ctype = np.array(tb_x, dtype=np.float).ctypes - tb_y_ctype = np.array(tb_y, dtype=np.float).ctypes assert len(tb_x) == len(tb_y) + tb_x_ctype, tb_y_ctype = self.tb_ctypes(tb_x, tb_y) self.lib.AddWindFarm(farm_name, turbine_model_path, turbine_model_name, len(tb_x), tb_x_ctype.data_as(c_double_p), tb_y_ctype.data_as(c_double_p)) @@ -55,7 +54,7 @@ class PyFuga(object): assert os.path.isfile(farms_dir + wind_atlas_path), farms_dir + wind_atlas_path self.lib.SetupWindClimate(farms_dir, wind_atlas_path, climate_interpolation) - #assert len(tb_x) == self.get_no_tubines(), self.log + "\n%d!=%d" % (self.get_no_tubines(),len(tb_x)) + assert len(tb_x) == self.get_no_turbines(), self.log + "\n%d!=%d" % (self.get_no_turbines(), len(tb_x)) def cleanup(self): if hasattr(self, 'lib'): @@ -72,16 +71,22 @@ class PyFuga(object): except Exception: pass - def get_no_tubines(self): + def get_no_turbines(self): no_turbines_p = c_int_p(c_int(0)) self.lib.GetNoTurbines(no_turbines_p) return no_turbines_p.contents.value - def move_turbines(self, tb_x, tb_y): - assert len(tb_x) == len(tb_y) == self.get_no_tubines(), (len(tb_x), len(tb_y), self.get_no_tubines()) - tb_x_ctype = np.array(tb_x, dtype=np.float).ctypes - tb_y_ctype = np.array(tb_y, dtype=np.float).ctypes + def tb_ctypes(self, tb_x, tb_y): + assert len(tb_x) == len(tb_y) + # remove mean offset to avoid loosing precision due to high offset + self.tb_x_offset, self.tb_y_offset = np.mean(tb_x), np.mean(tb_y) + tb_x = np.array(tb_x, dtype=np.float) - self.tb_x_offset + tb_y = np.array(tb_y, dtype=np.float) - self.tb_y_offset + return tb_x.ctypes, tb_y.ctypes + def move_turbines(self, tb_x, tb_y): + assert len(tb_x) == len(tb_y) == self.get_no_turbines(), (len(tb_x), len(tb_y), self.get_no_turbines()) + tb_x_ctype, tb_y_ctype = self.tb_ctypes(tb_x, tb_y) self.lib.MoveTurbines(tb_x_ctype.data_as(c_double_p), tb_y_ctype.data_as(c_double_p)) def get_aep(self, turbine_positions=None): @@ -92,7 +97,6 @@ class PyFuga(object): AEPGros_p = c_double_p(c_double(0)) capacity_p = c_double_p(c_double(0)) self.lib.GetAEP(AEPNet_p, AEPGros_p, capacity_p) - #print(tb_x, tb_y, AEPNet_p.contents.value, (15.850434458235156 - AEPNet_p.contents.value) / .000001) net, gros, cap = [p.contents.value for p in [AEPNet_p, AEPGros_p, capacity_p]] return (net, gros, cap, net / gros) @@ -100,15 +104,14 @@ class PyFuga(object): if turbine_positions is not None: self.move_turbines(turbine_positions[:, 0], turbine_positions[:, 1]) - n_wt = self.get_no_tubines() + n_wt = self.get_no_turbines() dAEPdxyz = np.zeros(n_wt), np.zeros(n_wt), np.zeros(n_wt) dAEPdxyz_ctype = [dAEP.ctypes for dAEP in dAEPdxyz] self.lib.GetAEPGradients(*[dAEP_ctype.data_as(c_double_p) for dAEP_ctype in dAEPdxyz_ctype]) - #print(tb_x, tb_y, dAEPdxyz) return np.array(dAEPdxyz) def get_TopFarm_cost_component(self): - n_wt = self.get_no_tubines() + n_wt = self.get_no_turbines() return AEPCostModelComponent(n_wt, lambda *args: self.get_aep(*args)[0], # only aep lambda *args: self.get_aep_gradients(*args)[:2]) # only dAEPdx and dAEPdy @@ -118,27 +121,6 @@ class PyFuga(object): with open(self.stdout_filename) as fid: return fid.read() -# self.execute("""seed=0 -# initialize -# set output file "out.txt" -# load farm "Horns Rev 1" -# 7 point integration off -# meandering off -# insert met mast 0.0 0.0 70.0 -# z0=0.0001 -# zi=400.0 -# zeta0=0.0 -# load wakes -# gaussian fit on -# proximity penalty off -# gradients on -# Wind climates interpolation on -# load wind atlas "Horns Rev 1\hornsrev2.lib" -# relative move all turbines -426733.0 -6149501.5 -# gradients off -# calculate AEP -# get Farm AEP""") - def execute(self, script): res = self.lib.ExecuteScript(script.encode()) print("#" + str(res) + "#") @@ -153,7 +135,7 @@ def try_me(): mast_position=(0, 0, 70), z0=0.0001, zi=400, zeta0=0, farms_dir=fuga_path + 'LUT/Farms/', wind_atlas_path='Horns Rev 1\hornsrev.lib') - print(pyFuga.get_no_tubines()) + print(pyFuga.get_no_turbines()) print(pyFuga.get_aep(np.array([[0, 0], [0, 1000]]))) print(pyFuga.get_aep(np.array([[0, 1000], [0, 0]]))) print(pyFuga.get_aep_gradients(np.array([[0, 0], [0, 100]])))