class MIMOEquation(Component): """Equation with 2 inputs and 2 outputs""" # pylint: disable-msg=E1101 x1 = Float(1.0, iotype='in', desc='Global Design Variable') x2 = Float(1.0, iotype='in', desc='Global Design Variable') x3 = Float(1.0, iotype='in', desc='Global Design Variable') x4 = Float(1.0, iotype='in', desc='Global Design Variable') x5 = Float(1.0, iotype='in', desc='Global Design Variable') f1 = Float(iotype='out', desc='Output of this Discipline') f2 = Float(iotype='out', desc='Output of this Discipline') f3 = Float(iotype='out', desc='Output of this Discipline') f4 = Float(iotype='out', desc='Output of this Discipline') f5 = Float(iotype='out', desc='Output of this Discipline') def execute(self): """Should converge to x=[0,0,0,0,0]""" xx = numpy.array([self.x1, self.x2, self.x3, self.x4, self.x5]) d = numpy.array([3, 2, 1.5, 1, 0.5]) c = 0.01 ff = -d * numpy.array(xx) - c * numpy.array(xx)**3 self.f1 = ff[0] self.f2 = ff[1] self.f3 = ff[2] self.f4 = ff[3] self.f5 = ff[4]
class DumbComp(Component): """A component whose output is independent of the input.""" # pylint: disable-msg=E1101 x1 = Float(1.0, iotype='in', desc='Global Design Variable') f1 = Float(3.14, iotype='out', desc='Output of this Discipline') def execute(self): """Do nothing""" pass
def setUp(self): """this setup function will be called before each test in this class""" self.hobj = Container() self.hobj.add('float1', Float(98.9, low=0., high=99.0, desc="Stuff", iotype='in', units='ft')) self.hobj.add('float2', Float(13.2, iotype='out', units='inch', low=-9999.)) self.hobj.add('float3', Float(low=0., high=99., iotype='in', units='kg')) self.hobj.float1 = 3.1415926 self.hobj.float2 = 42. self.hobj.float3 = 1.1
def test_attributes(self): try: self.hobj.add('badbounds', Float(98.0, low=100.0, high=0.0, iotype='in')) except Exception, err: errstring = "Lower bound is greater than upper bound." self.assertEqual(str(err), errstring)
def test_default_value_type(self): try: self.hobj.add('bad_default', Float('Bad Wolf')) except ValueError, err: self.assertEqual(str(err), "Default value should be a float.")
def test_default_value(self): try: self.hobj.add('out_of_bounds', Float(5.0, low=3, high=4)) except ValueError, err: self.assertEqual(str(err), "Default value is outside of bounds [3.0, 4.0].")
def test_constructor_defaults(self): self.hobj.add('float_nodefault1', Float(low=3.0, high=4.0, iotype='in', units='kg')) self.assertEqual(3.0, self.hobj.float_nodefault1) self.hobj.add('float_nodefault2', Float(high=4.0, iotype='in', units='kg')) self.assertEqual(4.0, self.hobj.float_nodefault2) self.hobj.add('float_nodefault3', Float(iotype='in', units='kg')) self.assertEqual(0.0, self.hobj.float_nodefault3) self.hobj.add('float_nounits', Float(low=3.0, high=4.0, iotype='in')) if hasattr(self.hobj.float_nounits, 'units'): self.fail("Unitless Float should not have units")
def test_int_limits(self): # Ensure limits that are ints don't cause something like this: # Trait 'symmetry_angle' must be a float in the range (0, 180] # but attempted value is 11.25 self.hobj.add('symmetry_angle', Float(low=0, exclude_low=True, high=180)) self.hobj.symmetry_angle = 11.25 self.assertEqual(self.hobj.symmetry_angle, 11.25)
def test_exclusions(self): self.hobj.add('float4', Float(low=3.0, high=4.0, \ exclude_low=True, exclude_high=True, \ iotype='in', units='kg')) try: self.hobj.float4 = 3.0 except ValueError, err: self.assertEqual(str(err), ": Variable 'float4' must be a float in the range (3.0, 4.0), but a value of 3.0 <type 'float'> was specified.")
def test_set_to_default(self): self.assertEqual(3.1415926, self.hobj.float1) self.hobj.add('float4', Float(iotype='in', units='kg')) self.assertEqual(0., self.hobj.float4) self.hobj.float4 = 6.5 self.assertEqual(6.5, self.hobj.float4) self.hobj.revert_to_defaults() self.assertEqual(98.9, self.hobj.float1) self.assertEqual(0., self.hobj.float4)
class SellarDiscipline1(Component): """Component containing Discipline 1""" # pylint: disable-msg=E1101 z1 = Float(0.0, iotype='in', desc='Global Design Variable') z2 = Float(0.0, iotype='in', desc='Global Design Variable') x1 = Float(0.0, iotype='in', desc='Local Design Variable') y2 = Float(0.0, iotype='in', desc='Disciplinary Coupling') y1 = Float(iotype='out', desc='Output of this Discipline') def execute(self): """Evaluates the equation y1 = z1**2 + z2 + x1 - 0.2*y2""" z1 = self.z1 z2 = self.z2 x1 = self.x1 y2 = self.y2 self.y1 = z1**2 + z2 + x1 - 0.2 * y2
class ElNetLength(TopfarmComponent): wt_positions = Array( [], unit='m', iotype='in', desc='Array of wind turbines attached to particular positions') #wt_layout = VarTree(GenericWindFarmTurbineLayout(), iotype='in', desc='wind turbine properties and layout') elnet_layout = Dict( iotype='out') #VarTree(GenericWindTurbineCableLayout(), iotype='out') elnet_length = Float(iotype='out', desc='The total inner cable length', unit='m') scaling = Float(1.0, iotype='in', desc='') def execute(self): elnet_layout = elnet(self.wt_positions) if self.scaling == 0.0: #using the first run as a baseline for scaling: self.scaling = sum(elnet_layout.values()) self.elnet_length = sum(elnet_layout.values()) / self.scaling self.elnet_layout = elnet_layout
class SellarDiscipline2(Component): """Component containing Discipline 2""" # pylint: disable-msg=E1101 z1 = Float(0.0, iotype='in', desc='Global Design Variable') z2 = Float(0.0, iotype='in', desc='Global Design Variable') y1 = Float(0.0, iotype='in', desc='Disciplinary Coupling') y2 = Float(iotype='out', desc='Output of this Discipline') def execute(self): """Evaluates the equation y1 = y1**(.5) + z1 + z2""" z1 = self.z1 z2 = self.z2 # Note: this may cause some issues. However, y1 is constrained to be # above 3.16, so lets just let it converge, and the optimizer will # throw it out y1 = abs(self.y1) self.y2 = y1**(.5) + z1 + z2
class FoundationLength(TopfarmComponent): wt_positions = Array( [], unit='m', iotype='in', desc='Array of wind turbines attached to particular positions') ##wt_layout = VarTree(GenericWindFarmTurbineLayout(), iotype='in', desc='wind turbine properties and layout') borders = Array(iotype='in', desc='The polygon defining the borders ndarray([n_bor,2])', unit='m') depth = Array(iotype='in', desc='An array of depth ndarray([n_d, 2])', unit='m') foundation_length = Float( iotype='out', desc='The total foundation length of the wind farm') foundations = Array(iotype='out', desc='The foundation length ofeach wind turbine') scaling = Float(1.0, iotype='in', desc='scaling of the foundation') inc = 0 def execute(self): foundation_func = LinearNDInterpolator(self.depth[:, 0:2], self.depth[:, 2]) self.foundations = array([ foundation_func(self.wt_positions[i, 0], self.wt_positions[i, 1]) for i in range(self.wt_positions.shape[0]) ]) #dist = DistFromBorders()(wt_positions=self.wt_positions, borders=self.borders).dist min_depth = self.depth[:, 2].min() self.foundations[isnan(self.foundations)] = min_depth if self.scaling == 0.0: # Using the baseline for scaling self.scaling = sum(self.foundations) self.foundation_length = sum(self.foundations) / self.scaling
def test_intvalues(self): f1 = Float(3,low=2,high=4) d1 = f1.default_value/2 self.assertAlmostEqual(d1, 1.5, places=4)
def test_bogus_units(self): try: uf = Float(0., iotype='in', units='bogus') except ValueError, err: self.assertEqual(str(err), "Units of 'bogus' are invalid")
class AEP(TopfarmComponent): # Inputs wind_speeds = List([], iotype='in', units='m/s', desc='The different wind speeds to run [nWS]') wind_directions = List([], iotype='in', units='deg', desc='The different wind directions to run [nWD]') wt_positions = Array(iotype='in') scaling = Float(1.0, iotype='in', desc='Scaling of the AEP') # Outputs array_aep = Array([], iotype='out', units='kW*h', desc='The energy production per sector [nWD, nWS]') gross_aep = Float(iotype='out', units='kW*h', desc='Gross Annual Energy Production before availability and loss impacts') net_aep = Float(iotype='out', units='kW*h', desc='Net Annual Energy Production after availability and loss impacts') capacity_factor = Float(0.0, iotype='out', desc='Capacity factor for wind plant') def __init__(self, wt_layout, wind_rose, wf, **kwargs): """ :param wt_layout: GenericWindFarmTurbineLayout() :param wind_rose: WeibullWindRoseVT() :param wf: GenericWindFarm() :param scaling: float [default = 1.0] The scaling used to calculate the net_aep. If it is set to 0.0, the scaling will be set to the net_aep the first time the simulation is run. """ self.wf = wf self.wf.wt_layout = wt_layout self.wind_rose = wind_rose super(AEP, self).__init__(**kwargs) def execute(self): # build the cdf vector of the wind speed for each wind rose wind direction sector cdfw = [] for iwd, wd in enumerate(self.wind_rose.wind_directions): cdfw.append(weibullCDF(self.wind_speeds, self.wind_rose.A[iwd], self.wind_rose.k[iwd])) # calculate the probability in each wind direction sector, using the CDF of the wind rose wind direction cdfd0 = [sum(self.wind_rose.frequency[:i]) for i in range(len(self.wind_rose.frequency)+1)] wd = np.hstack([self.wind_rose.wind_directions, [360]]) cdfd1 = interp1d(wd, cdfd0)(self.wind_directions) net_aep = 0.0 gross_aep = 0.0 cwd = 0 net_aeps = np.zeros([len(self.wind_directions)]) gross_aeps = np.zeros([len(self.wind_directions)]) self.powers = np.zeros([len(self.wind_directions), len(self.wind_speeds)]) for iwd, wd in enumerate(self.wind_directions): if cwd < len(self.wind_rose.wind_directions): while wd >= self.wind_rose.wind_directions[cwd+1] and cwd < len(self.wind_rose.wind_directions)-2: # switching wind rose wind direction sector cwd += 1 powers = np.zeros([len(self.wind_speeds)]) for iws, ws in enumerate(self.wind_speeds): self.wf.wt_layout.wt_positions=self.wt_positions self.wf.wind_speed=ws self.wf.wind_direction=wd self.wf.run() powers[iws] = self.wf.power self.powers[iwd,:] = powers # Integrating over the wind speed CDF net_aeps[iwd] = np.trapz(powers, cdfw[cwd]) * 365.0 * 24.0 for i in range(self.wt_positions.shape[0]): power_curve = interp1d(self.wf.wt_layout.wt_list[i].power_curve[:,0], self.wf.wt_layout.wt_list[i].power_curve[:,1])(self.wind_speeds) gross_aeps[iwd] += np.trapz(power_curve, cdfw[cwd]) * 365.0 * 24.0 # Integrating over the wind direction CDF net_aep = np.trapz(net_aeps, cdfd1) gross_aep = np.trapz(gross_aeps, cdfd1) self.capacity_factor = net_aep / gross_aep if self.scaling == 0.0: # The scaling has to be calculated self.scaling = net_aep self.net_aep = net_aep / self.scaling self.gross_aep = gross_aep
class AEPFortranMultipleWindRoses(TopfarmComponent): # Inputs wind_speeds = List([], iotype='in', units='m/s', desc='The different wind speeds to run [nWS]') wind_directions = List([], iotype='in', units='deg', desc='The different wind directions to run [nWD]') turbulence_intensity = Float(0.1, iotype='in', desc='The turbulence intensity at the site') wt_positions = Array(iotype='in') wind_roses = List(iotype='in', desc='List of weibull wind_rose arrays') scaling = Float(1.0, iotype='in', desc='Scaling of the AEP') # Outputs array_aep = Array([], iotype='out', units='kW*h', desc='The energy production per sector [nWD, nWS]') gross_aep = Float(iotype='out', units='kW*h', desc='Gross Annual Energy Production before availability and loss impacts') net_aep = Float(iotype='out', units='kW*h', desc='Net Annual Energy Production after availability and loss impacts') capacity_factor = Float(0.0, iotype='out', desc='Capacity factor for wind plant') def __init__(self, wt_layout, wf, **kwargs): """ :param wt_layout: GenericWindFarmTurbineLayout() :param wf: GenericWindFarm() :param scaling: float [default = 1.0] The scaling used to calculate the net_aep. If it is set to 0.0, the scaling will be set to the net_aep the first time the simulation is run. """ self.wf = wf self.wf.wt_layout = wt_layout #self.wind_rose = wind_rose super(AEPFortranMultipleWindRoses, self).__init__(**kwargs) def execute(self): wt_net_aep = np.zeros([self.wt_positions.shape[0]]) wt_gross_aep = np.zeros([self.wt_positions.shape[0]]) cwd = 0 wind_speeds, wind_directions = np.meshgrid(self.wind_speeds, self.wind_directions) # Runnning all the wind speeds and wind directions at the same time self.wf.wt_layout.wt_positions=self.wt_positions self.wf.wind_speeds = wind_speeds.flatten() self.wf.wind_directions = wind_directions.flatten() self.wf.turbulence_intensities = self.turbulence_intensity * ones_like(self.wf.wind_directions) self.wf.run() powers = self.wf.wt_power.reshape([len(self.wind_directions), len(self.wind_speeds), self.wt_positions.shape[0]]) self.powers = powers for iwt, wt in enumerate(self.wf.wt_layout.wt_list): net_aeps = np.zeros([len(self.wind_directions)]) gross_aeps = np.zeros([len(self.wind_directions)]) # build the cdf vector of the wind speed for each wind rose wind direction sector cdfw = [] for iwd, wd in enumerate(self.wind_roses[iwt].wind_directions): cdfw.append(weibullCDF(self.wind_speeds, self.wind_roses[iwt].A[iwd], self.wind_roses[iwt].k[iwd])) # calculate the probability in each wind direction sector, using the CDF of the wind rose wind direction cdfd0 = [sum(self.wind_roses[iwt].frequency[:i]) for i in range(len(self.wind_roses[iwt].frequency)+1)] wd = np.hstack([self.wind_roses[iwt].wind_directions, [360]]) cdfd1 = interp1d(wd, cdfd0)(self.wind_directions) cwd = 0 for iwd, wd in enumerate(self.wind_directions): # self.wind_directions is not the same as self.wind_roses[iwt].wind_directions, so we have to match both sectors if cwd < len(self.wind_roses[iwt].wind_directions): while wd >= self.wind_roses[iwt].wind_directions[cwd+1] and \ cwd < len(self.wind_roses[iwt].wind_directions)-2: # switching wind rose wind direction sector cwd += 1 # Integrating over the wind speed CDF net_aeps[iwd] = np.trapz(powers[iwd,:,iwt], cdfw[cwd]) * 365.0 * 24.0 for i in range(self.wt_positions.shape[0]): power_curve = interp1d(wt.power_curve[:,0], wt.power_curve[:,1])(self.wind_speeds) gross_aeps[iwd] += np.trapz(power_curve, cdfw[cwd]) * 365.0 * 24.0 # Integrating over the wind direction CDF wt_net_aep[iwt] = np.trapz(net_aeps, cdfd1) wt_gross_aep[iwt] = np.trapz(gross_aeps, cdfd1) net_aep = wt_net_aep.sum() gross_aep = wt_gross_aep.sum() self.capacity_factor = net_aep / gross_aep if self.scaling == 0.0: # The scaling has to be calculated self.scaling = net_aep self.net_aep = net_aep / self.scaling self.gross_aep = gross_aep