• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NREL / WindSE / 5524291866

pending completion
5524291866

push

github

web-flow
Update main (#104)

* minor change to warning text

* shift gauss cyld by RD

* fixed cyld, converted point to expression

* fixed jit problem

* added sharpness param to point blockage

* fixed u_k update for constraints

* fixed taylor test convergence

* Added check_totals option

* fixed constraint derivatives, and taylor test inputs

* ALM DEL method

* default params

* updating yaml params

* moving example to demo folder

* renaming example folder

* starting on acuator disks

* fixed inflow angle bug

* made the multiangle solver more robust

* fixing what the fix broke

* yep

* Added fatpack to the install script

* added pandas

* got dolfin actuator disks working

* gotta fix a form_compiler issue, but it seems all good up to the gradient

* got numpy disks working

* added stop_annotation to the debug, and changed how boundary HH velocity calculated

* updated some demos/test, fixed objectives

* initial commit to compare to refactored ALM output to dev

* test push upstream

* minor cleaning after verifying results match

* adding power calc and more cleanup

* getting ready for optimization test

* adding test file for collaborating on optimization

* storing before opt testing original branch

* pushing latest mpi_u_fluid deriv testing

* adding latest test updates

* cleaning up ALM block tests

* syncing print statements for jeff

* refactored imported wind farms

* added empty farm, updated demos/test/docs

* fixed alm adjoint bug, got all test running

* moved test_alm_refactor.yaml to other folder so it doesn't get picked up by pytest

* fixing conflicts before factoring out trans/rots

* wrap values e.g. self.mz as float to avoid jit error

* factoring translation/rotation out of u-fluid and alm

* consistent var names prev vs behind

* fix alm gradients

* take two

* fixing merge conflicts

* fixing mz dijitso error

* added missi... (continued)

4003 of 4003 new or added lines in 47 files covered. (100.0%)

4546 of 7192 relevant lines covered (63.21%)

0.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.67
/windse/ProblemManager.py
1
"""
2
The ProblemManager contains all of the 
3
different classes of problems that windse can solve
4
"""
5

6
import __main__
1✔
7
import os
1✔
8
from pyadjoint.tape import no_annotations
1✔
9

10
### Get the name of program importing this package ###
11
if hasattr(__main__,"__file__"):
1✔
12
    main_file = os.path.basename(__main__.__file__)
1✔
13
else:
14
    main_file = "ipython"
×
15
    
16
### This checks if we are just doing documentation ###
17
if not main_file in ["sphinx-build", "__main__.py"]:
1✔
18
    from dolfin import *
1✔
19
    import numpy as np
1✔
20
    import time
1✔
21
    import scipy.interpolate as interp
1✔
22
    import glob
1✔
23

24
    ### Import the cumulative parameters ###
25
    from windse import windse_parameters
1✔
26
    # from memory_profiler import memory_usage
27

28
    ### Check if we need dolfin_adjoint ###
29
    if windse_parameters.dolfin_adjoint:
1✔
30
        from dolfin_adjoint import *
1✔
31

32
class GenericProblem(object):
1✔
33
    """
34
    A GenericProblem contains on the basic functions required by all problem objects.
35
    
36
    Args: 
37
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
38
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
39
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
40
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
41
    """
42
    def __init__(self,domain,windfarm,function_space,boundary_data):
1✔
43
        ### save a reference of option and create local version specifically of domain options ###
44
        self.params = windse_parameters
1✔
45
        self.dom  = domain
1✔
46
        self.farm = windfarm
1✔
47
        self.fs   = function_space 
1✔
48
        self.bd  = boundary_data
1✔
49
        self.tf_first_save = True
1✔
50
        self.fprint = self.params.fprint
1✔
51
        self.tag_output = self.params.tag_output
1✔
52
        self.debug_mode = self.params.debug_mode
1✔
53

54
        ### Update attributes based on params file ###
55
        for key, value in self.params["problem"].items():
1✔
56
            setattr(self,key,value)
1✔
57

58
        self.record_time = self.params["optimization"].get("record_time",0.0)
1✔
59
        if isinstance(self.record_time,str):
1✔
60
            self.record_time = 0.0
×
61

62
        self.extra_kwarg = {}            
1✔
63
        if self.params.dolfin_adjoint:
1✔
64
            self.extra_kwarg["annotate"] = False
1✔
65

66
        # Setup body force
67
        self.mbody_force = Constant(self.body_force)
1✔
68

69

70
    @no_annotations
1✔
71
    def DebugOutput(self):
1✔
72
        if self.debug_mode:
1✔
73
            # integral of nu_t
74
            int_nut = assemble(self.nu_T*dx)/self.dom.volume
1✔
75
            self.tag_output("int_nu_T", int_nut)
1✔
76

77
            if self.dom.dim == 3:
1✔
78
                e1 = Constant((1,0,0)); e2 = Constant((0,1,0)); e3 = Constant((0,0,1));
1✔
79
            else:
80
                e1 = Constant((1,0)); e2 = Constant((0,1));
1✔
81

82
            int_tf_x = assemble(inner(self.tf,e1)*dx)/self.dom.volume
1✔
83
            self.tag_output("int_tf_x", int_tf_x)
1✔
84
            int_tf_y = assemble(inner(self.tf,e2)*dx)/self.dom.volume
1✔
85
            self.tag_output("int_tf_y", int_tf_y)
1✔
86
            if self.dom.dim == 3:
1✔
87
                int_tf_z = assemble(inner(self.tf,e3)*dx)/self.dom.volume
1✔
88
                self.tag_output("int_tf_z", int_tf_z)
1✔
89

90
                if self.farm.turbine_type == 'line':
1✔
91
                    chord = []
1✔
92
                    cl = []
1✔
93
                    cd = []
1✔
94
                    num_actuator_nodes = []
1✔
95
                    for i in range(self.farm.numturbs):
1✔
96
                        chord.append(self.farm.turbines[i].chord)
1✔
97
                        cl.append(self.farm.turbines[i].cl)
1✔
98
                        cd.append(self.farm.turbines[i].cd)
1✔
99
                        num_actuator_nodes.append(self.farm.turbines[i].num_actuator_nodes)
1✔
100

101
                    self.tag_output("min_chord", np.min(chord))
1✔
102
                    self.tag_output("max_chord", np.max(chord))
1✔
103
                    self.tag_output("avg_chord", np.mean(chord))
1✔
104
                    self.tag_output("min_cl", np.min(cl))
1✔
105
                    self.tag_output("max_cl", np.max(cl))
1✔
106
                    self.tag_output("avg_cl", np.mean(cl))
1✔
107
                    self.tag_output("min_cd", np.min(cd))
1✔
108
                    self.tag_output("max_cd", np.max(cd))
1✔
109
                    self.tag_output("avg_cd", np.mean(cd))
1✔
110
                    self.tag_output("num_actuator_nodes", np.mean(num_actuator_nodes))
1✔
111
                
112

113
    def ComputeTurbineForce(self,u,inflow_angle,**kwargs):
1✔
114
        tf_start = time.time()
1✔
115
        self.fprint("Calculating Turbine Force",special="header")
1✔
116

117
        ### Compute the relative yaw angle ###
118
        if inflow_angle is None:
1✔
119
            inflow_angle = self.dom.inflow_angle
×
120

121
        self.fprint('Computing turbine forces using %s' % (self.farm.turbine_type.upper()))
1✔
122

123
        ### Create the turbine force function ###
124
        if self.farm.turbine_type == "disabled" or self.farm.numturbs == 0:
1✔
125
            tf = Function(self.fs.V)
×
126
        else:
127
            tf = self.farm.compute_turbine_force(u,inflow_angle,self.fs,**kwargs)
1✔
128

129
        tf_stop = time.time()
1✔
130
        self.fprint("Turbine Force Calculated: {:1.2f} s".format(tf_stop-tf_start),special="footer")
1✔
131
        return tf
1✔
132

133
    def ComputeTurbulenceModel(self, u):
1✔
134
        self.fprint(f"Using Turbulence Model: {self.turbulence_model}")
1✔
135
        if self.turbulence_model is not None:
1✔
136
            # Calculate eddy viscosity, if using
137
            # self.turbulence_model = 'smagorinsky'
138
            # self.turbulence_model = 'mixing_length'
139
            # self.turbulence_model = None
140

141
            # Strain rate tensor, 0.5*(du_i/dx_j + du_j/dx_i)
142
            Sij = sym(grad(u))
1✔
143

144
            # sqrt(Sij*Sij)
145
            strainMag = (2.0*inner(Sij, Sij))**0.5
1✔
146

147
            if self.turbulence_model == 'smagorinsky':
1✔
148
                # Smagorinsky constant, typically around 0.17
149
                Cs = 0.17
1✔
150

151
                # Define filter scale (all models use this)
152
                filter_scale = CellVolume(self.dom.mesh)**(1.0/self.dom.dim)
1✔
153

154
                # Eddy viscosity
155
                nu_T = Cs**2 * filter_scale**2 * strainMag
1✔
156

157
            elif self.turbulence_model == 'mixing_length':
1✔
158
                # von Karman constant
159
                vonKarman = 0.41
1✔
160

161
                if self.dom.dim == 3:
1✔
162
                    depth = self.bd.depth.vector()[:]
1✔
163
                else:
164
                    if self.farm.numturbs == 0:
1✔
165
                        depth = self.params["boundary_conditions"]["vel_height"]
×
166
                    else:
167
                        depth = self.farm.turbines[0].HH
1✔
168

169
                numer = vonKarman*depth
1✔
170
                denom = 1.0 + vonKarman*depth/self.lmax
1✔
171

172
                l_mix = Function(self.fs.Q)
1✔
173
                l_mix.vector()[:] = numer/denom
1✔
174
                l_mix.rename("l_mix","l_mix")
1✔
175

176
                # Eddy viscosity
177
                nu_T = l_mix**2 * strainMag
1✔
178

179
        else:
180
            nu_T = Constant(0)
×
181

182
        return nu_T
1✔
183

184

185
    ############################################
186
    ############################################
187
    ### this is a hack to accommodate the fact that
188
    ### alm values are not stored in the wind_farm
189
    ### object. eventually we might want to move
190
    ### it there.
191
    ############################################
192
    ############################################
193
    def CopyALMtoWindFarm(self):
1✔
194
        self.farm.mcl = self.mcl
×
195
        self.farm.mcd = self.mcd
×
196
        self.farm.mchord = self.mchord
×
197
        self.farm.cl = self.cl
×
198
        self.farm.cd = self.cd
×
199
        self.farm.chord = self.chord
×
200
        self.farm.num_actuator_nodes = self.num_actuator_nodes
×
201

202
    def SimpleControlUpdate(self):
1✔
203
        self.u_k, self.p_k = split(self.up_k)
×
204
        self.farm.SimpleControlUpdate()
×
205

206

207
    def ChangeWindAngle(self,inflow_angle):
1✔
208
        """
209
        This function recomputes all necessary components for a new wind direction
210

211
        Args: 
212
            inflow_angle (float): The new wind angle in radians
213
        """
214
        adj_start = time.time()
×
215
        self.fprint("Adjusting Wind Angle",special="header")
×
216
        self.fprint("New Angle: {:1.8f} rads".format(inflow_angle))
×
217
        self.dom.RecomputeBoundaryMarkers(inflow_angle)
×
218
        self.bd.RecomputeVelocity(inflow_angle)
×
219
        self.ComputeFunctional(inflow_angle)
×
220

221
        adj_stop = time.time()
×
222
        self.fprint("Wind Angle Adjusted: {:1.2f} s".format(adj_stop-adj_start),special="footer")
×
223

224
        # self.tf.assign(tf_temp)
225

226
    def ChangeWindSpeed(self,inflow_speed):
1✔
227
        adj_start = time.time()
×
228
        self.fprint("Adjusting Wind Speed",special="header")
×
229
        self.fprint("New Speed: {:1.8f} m/s".format(inflow_speed/self.dom.xscale))
×
230
        self.bd.HH_vel = inflow_speed
×
231
        adj_stop = time.time()
×
232
        self.fprint("Wind Speed Adjusted: {:1.2f} s".format(adj_stop-adj_start),special="footer")
×
233

234
    def UpdateActuatorLineControls(self, c_lift = None, c_drag = None, chord = None, yaw = None, turb_i = 0):
1✔
235

236
        if c_lift is not None:
×
237
            cl = np.array(c_lift, dtype = float)
×
238
            self.cl[turb_i] = cl
×
239
            for k in range(self.num_actuator_nodes):
×
240
                self.mcl[turb_i][k] = Constant(cl[k])
×
241
        if c_drag is not None:
×
242
            cd = np.array(c_drag, dtype = float)
×
243
            self.cd[turb_i] = cd
×
244
            for k in range(self.num_actuator_nodes):
×
245
                self.mcd[turb_i][k] = Constant(cd[k])
×
246
        if chord is not None:
×
247
            chord = np.array(chord, dtype = float)
×
248
            self.chord[turb_i] = chord
×
249
            for k in range(self.num_actuator_nodes):
×
250
                self.mchord[turb_i][k] = Constant(chord[k])
×
251
        if yaw is not None:
×
252
            yaw = float(yaw)
×
253
            self.farm.yaw[turb_i] = yaw
×
254
            self.farm.myaw[turb_i] = Constant(yaw)
×
255
        
256

257
        self.CopyALMtoWindFarm()
×
258

259

260
class StabilizedProblem(GenericProblem):
1✔
261
    """
262
    The StabilizedProblem setup everything required for solving Navier-Stokes with 
263
    a stabilization term
264

265
    Args: 
266
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
267
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
268
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
269
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
270
    """
271
    def __init__(self,domain,windfarm,function_space,boundary_conditions):
1✔
272
        super(StabilizedProblem, self).__init__(domain,windfarm,function_space,boundary_conditions)
1✔
273
        
274
        ### Create Functional ###
275
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
276
        self.DebugOutput()
1✔
277

278

279
    def ComputeFunctional(self,inflow_angle):
1✔
280
        self.fprint("Setting Up Stabilized Problem",special="header")
1✔
281

282
        ### Create the test/trial/functions ###
283
        self.up_k = Function(self.fs.W)
1✔
284
        self.u_k,self.p_k = split(self.up_k)
1✔
285
        v,q = TestFunctions(self.fs.W)
1✔
286

287
        ### Set the x scaling ###
288
        Sx = self.dom.xscale
1✔
289

290
        ### Set the initial guess ###
291
        u0 = self.bd.u0
1✔
292
        self.up_k.assign(u0)
1✔
293

294
        # mem0=memory_usage()[0]
295
        # mem_out, self.tf = memory_usage((self.ComputeTurbineForce,(self.u_k,inflow_angle),{}),max_usage=True,retval=True,max_iterations=1)
296
        # self.fprint("Memory Used:  {:1.2f} MB".format(mem_out-mem0))
297
        self.tf = self.ComputeTurbineForce(self.u_k,inflow_angle)
1✔
298

299
        ### These constants will be moved into the params file ###
300
        f = Constant((0.0,)*self.dom.dim)
1✔
301
        f.rename("f","f")
1✔
302
        
303
        nu = self.viscosity
1✔
304
        vonKarman=0.41
1✔
305
        eps=Constant(self.stability_eps)
1✔
306
        eps.rename("eps","eps")
1✔
307

308
        self.fprint("Viscosity:                 {:1.2e}".format(float(self.viscosity)))
1✔
309
        self.fprint("Max Mixing Length:         {:1.2e}".format(float(self.lmax)))
1✔
310
        self.fprint("Stabilization Coefficient: {:1.2e}".format(float(eps)))
1✔
311

312
        ### Calculate nu_T
313
        self.nu_T=self.ComputeTurbulenceModel(self.u_k)
1✔
314
        # self.nu_T=Constant(0.0)
315
        self.ReyStress=self.nu_T*grad(self.u_k)
1✔
316
        self.vertKE= self.ReyStress[0,2]*self.u_k[0]
1✔
317

318
        ### Create the functional ###
319
        self.F = inner(grad(self.u_k)*self.u_k, v)*dx
1✔
320
        self.F +=   Sx*Sx*(nu+self.nu_T)*inner(grad(self.u_k), grad(v))*dx
1✔
321
        self.F += - inner(div(v),self.p_k)*dx
1✔
322
        self.F += - inner(div(self.u_k),q)*dx
1✔
323
        self.F += - inner(f,v)*dx
1✔
324
        self.F += - inner(self.tf,v)*dx 
1✔
325

326

327
        # Add body force to functional
328
        if abs(float(self.mbody_force)) >= 1e-14:
1✔
329
            self.fprint("Using Body Force")
×
330
            self.F += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
331

332
        ################ THIS IS A CHEAT ####################\
333
        if self.use_corrective_force:
1✔
334
            self.fprint("Using Corrective Force")
×
335
            extra_S = sqrt(2*inner(0.5*(grad(self.bd.bc_velocity)+grad(self.bd.bc_velocity).T),0.5*(grad(self.bd.bc_velocity)+grad(self.bd.bc_velocity).T)))
×
336
            extra_l_mix = Function(self.fs.Q)
×
337
            extra_l_mix.vector()[:] = np.divide(vonKarman*self.bd.depth.vector()[:]/Sx,(1.+np.divide(vonKarman*self.bd.depth.vector()[:]/Sx,self.lmax)))
×
338
            extra_nu_T = extra_l_mix**2.*extra_S
×
339
            extra_DP =dot(self.bd.u0,grad(self.bd.u0)) - div((nu+extra_nu_T)*grad(self.bd.bc_velocity))
×
340

341
            self.F += inner(extra_DP,v)*dx
×
342
        ########################################################
343

344
        # self.F_sans_tf =  (1.0)*inner(grad(self.u_k), grad(v))*dx - inner(div(v),self.p_k)*dx - inner(div(self.u_k),q)*dx - inner(f,v)*dx
345
        # self.F = inner(grad(self.u_k)*self.u_k, v)*dx + (nu+self.nu_T)*inner(grad(self.u_k), grad(v))*dx - inner(div(v),self.p_k)*dx - inner(div(self.u_k),q)*dx - inner(f,v)*dx - inner(self.tf*(self.u_k[0]**2+self.u_k[1]**2),v)*dx 
346
        # stab_sans_tf = - eps*inner(grad(q), grad(self.p_k))*dx 
347
        # self.F_sans_tf += stab
348

349
        ### Add in the Stabilizing term ###
350
        if abs(float(eps)) >= 1e-14:
1✔
351
            self.fprint("Using Stabilization Term")
1✔
352
            stab = - eps*inner(grad(q), grad(self.p_k))*dx - eps*inner(grad(q), dot(grad(self.u_k), self.u_k))*dx 
1✔
353
            self.F += stab
1✔
354

355

356
        if self.use_25d_model and self.dom.dim == 2 :
1✔
357
            if self.dom.dim == 3:
×
358
                raise ValueError("The 2.5D model requires a 2D simulation.")
×
359

360
            self.fprint("Using 2.5D model")
×
361
            dudx = Dx(self.u_next[0], 0)
×
362
            dvdy = Dx(self.u_next[1], 1)
×
363

364
            if inflow_angle is None:
×
365
                term25 = dvdy*q*dx
×
366
            else:
367
                term25 = (abs(sin(inflow_angle))*dudx*q + abs(cos(inflow_angle))*dvdy*q)*dx
×
368

369
            self.F -= term25
×
370

371
        self.fprint("Stabilized Problem Setup",special="footer")
1✔
372

373

374
class TaylorHoodProblem(GenericProblem):
1✔
375
    """
376
    The TaylorHoodProblem sets up everything required for solving Navier-Stokes 
377

378
    Args: 
379
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
380
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
381
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
382
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
383
    """
384
    def __init__(self,domain,windfarm,function_space,boundary_conditions):
1✔
385
        super(TaylorHoodProblem, self).__init__(domain,windfarm,function_space,boundary_conditions)
1✔
386

387
        ### Create Functional ###
388
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
389
        self.DebugOutput()
1✔
390

391
    def ComputeFunctional(self,inflow_angle):
1✔
392
        self.fprint("Setting Up Taylor-Hood Problem",special="header")
1✔
393

394
        ### These constants will be moved into the params file ###
395
        f = Constant((0.0,)*self.dom.dim)
1✔
396
        vonKarman=0.41
1✔
397
        eps=Constant(0.01)
1✔
398
        nu = self.viscosity
1✔
399

400

401
        self.fprint("Viscosity:         {:1.2e}".format(float(self.viscosity)))
1✔
402
        self.fprint("Max Mixing Length: {:1.2e}".format(float(self.lmax)))
1✔
403

404
        ### Create the test/trial/functions ###
405
        self.up_k = Function(self.fs.W)
1✔
406
        self.u_k,self.p_k = split(self.up_k)
1✔
407
        v,q = TestFunctions(self.fs.W)
1✔
408

409
        ### Set the initial guess ###
410
        ### (this will become a separate function.)
411
        self.up_k.assign(self.bd.u0)
1✔
412

413
        ### Calculate nu_T
414
        self.nu_T=self.ComputeTurbulenceModel(self.u_k)
1✔
415

416
        ### Create the turbine force ###
417
        self.tf = self.ComputeTurbineForce(self.u_k,inflow_angle)
1✔
418

419
        ### Create the functional ###
420
        self.F = inner(grad(self.u_k)*self.u_k, v)*dx + (nu+self.nu_T)*inner(grad(self.u_k), grad(v))*dx - inner(div(v),self.p_k)*dx - inner(div(self.u_k),q)*dx - inner(f,v)*dx - inner(self.tf,v)*dx 
1✔
421

422
        # Add body force to functional
423
        if abs(float(self.mbody_force)) >= 1e-14:
1✔
424
            self.fprint("Using Body Force")
×
425
            self.F += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
426

427
        if self.use_25d_model:
1✔
428
            if self.dom.dim == 3:
1✔
429
                raise ValueError("The 2.5D model requires a 2D simulation.")
×
430

431
            self.fprint("Using 2.5D model")
1✔
432
            dudx = Dx(self.u_k[0], 0)
1✔
433
            dvdy = Dx(self.u_k[1], 1)
1✔
434

435
            if inflow_angle is None:
1✔
436
                term25 = dvdy*q*dx
×
437
            else:
438
                term25 = (abs(sin(inflow_angle))*dudx*q + abs(cos(inflow_angle))*dvdy*q)*dx
1✔
439

440
            self.F -= term25
1✔
441

442
        self.fprint("Taylor-Hood Problem Setup",special="footer")
1✔
443

444
# ================================================================
445

446
class IterativeSteady(GenericProblem):
1✔
447
    """
448
    The IterativeSteady sets up everything required for solving Navier-Stokes using
449
    the SIMPLE algorithm
450

451
    Args: 
452
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
453
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
454
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
455
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
456
    """
457
    def __init__(self, domain, windfarm, function_space, boundary_conditions):
1✔
458
        super(IterativeSteady, self).__init__(domain, windfarm, function_space, boundary_conditions)
×
459
        self.fprint("Setting Up *Iterative* Steady Problem", special="header")
×
460

461
        ### Create Functional ###
462
        self.ComputeFunctional(self.dom.inflow_angle)
×
463

464
    def ComputeFunctional(self,inflow_angle):
1✔
465
        # ================================================================
466

467
        # Define fluid properties
468
        # FIXME: These should probably be set in params.yaml input filt
469
        # nu = 1/10000
470
        nu = Constant(self.viscosity)
×
471

472
        # Trial functions for velocity and pressure
473
        u = TrialFunction(self.fs.V)
×
474
        p = TrialFunction(self.fs.Q)
×
475

476
        # Test functions for velocity and pressure
477
        v = TestFunction(self.fs.V)
×
478
        q = TestFunction(self.fs.Q)
×
479

480
        ### Define Velocity Functions ###
481
        self.u_k = Function(self.fs.V, name="u_k")
×
482
        self.u_hat = Function(self.fs.V)
×
483
        self.u_s = Function(self.fs.V)
×
484
        self.du = Function(self.fs.V)
×
485
        self.u_k_old = Function(self.fs.V)
×
486

487
        ### Define Pressure Functions ###
488
        self.p_k = Function(self.fs.Q, name="p_k")
×
489
        self.p_s = Function(self.fs.Q)
×
490
        self.dp = Function(self.fs.Q)
×
491
        self.p_k_old = Function(self.fs.Q)
×
492

493
        U_CN = 0.5*(u + self.u_k) 
×
494

495
        # Adams-Bashforth velocity
496
        # U_AB = 1.5*u_k - 0.5*u_k_old # Time level k+1/2
497
        U_AB = 2.0*self.u_k - 1.0*self.u_k_old # Time level k+1
×
498

499
        # Compute eddy viscosity
500
        self.nu_T=self.ComputeTurbulenceModel(U_AB)
×
501

502
        # ================================================================
503

504
        # FIXME: This up_k function is only present to avoid errors  
505
        # during assignments in GenericSolver.__init__
506

507
        # Create the combined function space
508
        self.up_k = Function(self.fs.W)
×
509

510
        # Create the turbine force
511
        # FIXME: Should this be set by a numpy array operation or a fenics function?
512
        # self.tf = self.farm.TurbineForce(self.fs, self.dom.mesh, self.u_k2)
513
        # self.tf = Function(self.fs.V)
514

515
        self.tf = self.ComputeTurbineForce(self.u_k,inflow_angle)
×
516
        # self.u_k.assign(self.bd.bc_velocity)
517

518
        # self.u_k2.vector()[:] = 0.0
519
        # self.u_k1.vector()[:] = 0.0
520

521
        # ================================================================
522

523
        # Solve for u_hat, a velocity estimate which doesn't include pressure gradient effects
524
        F1 = inner(dot(self.u_k, nabla_grad(u)), v)*dx \
×
525
           + (nu+self.nu_T)*inner(grad(u), grad(v))*dx \
526
           - inner(self.tf, v)*dx 
527

528
        # Add body force to functional
529
        F1 += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
530

531
        self.F1_lhs = lhs(F1)
×
532
        self.F1_rhs = rhs(F1)
×
533

534

535
        # Use u_hat to solve for the pressure field
536
        self.F2_lhs = inner(grad(p), grad(q))*dx
×
537
        self.F2_rhs = - div(self.u_hat)*q*dx
×
538

539
        self.dt_1 = Constant(1) # Becomes (1/dt, 0) for (unsteady, steady) state
×
540
        self.dt_2 = Constant(1) # Becomes (1/dt, 1) for (unsteady, steady) state
×
541
        self.dt_3 = Constant(1) # Becomes (  dt, 1) for (unsteady, steady) state
×
542

543
        # Solve for u_star, a predicted velocity which includes the pressure gradient
544
        F3 = inner(dot(self.u_k, nabla_grad(u)), v)*dx \
×
545
           + (nu+self.nu_T)*inner(grad(u), grad(v))*dx \
546
           - inner(self.tf, v)*dx \
547
           + inner(grad(self.p_k), v)*dx \
548
           + self.dt_1*inner(u - self.u_k, v)*dx
549

550
        # Add body force to functional
551
        F3 += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
552

553
        self.F3_lhs = lhs(F3)
×
554
        self.F3_rhs = rhs(F3)
×
555

556

557
        # Define variational problem for step 2: pressure correction
558
        self.F4_lhs = inner(grad(p), grad(q))*dx
×
559
        self.F4_rhs = - self.dt_2*div(self.u_s)*q*dx
×
560

561

562
        # Define variational problem for step 3: velocity update
563
        self.F5_lhs = inner(u, v)*dx
×
564
        self.F5_rhs = - self.dt_3*inner(grad(self.dp), v)*dx
×
565

566
        # ================================================================
567

568
        self.fprint("*Iterative* Steady Problem Setup",special="footer")
×
569

570
# ================================================================
571

572
class UnsteadyProblem(GenericProblem):
1✔
573
    """
574
    The UnsteadyProblem sets up everything required for solving Navier-Stokes using
575
    a fractional-step method with an adaptive timestep size
576

577
    Args: 
578
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
579
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
580
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
581
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
582
    """
583
    def __init__(self, domain, windfarm, function_space, boundary_conditions):
1✔
584
        super(UnsteadyProblem, self).__init__(domain, windfarm, function_space, boundary_conditions)
1✔
585
        self.fprint("Setting Up Unsteady Problem", special="header")
1✔
586

587
        ### Create Functional ###
588
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
589
        self.DebugOutput()
1✔
590

591
    def ComputeFunctional(self,inflow_angle):
1✔
592
        # ================================================================
593

594
        # Define fluid properties
595
        # FIXME: These should probably be set in params.yaml input filt
596
        # nu = 1/10000
597
        rho = 1
1✔
598
        nu_c = Constant(self.viscosity, name="viscosity")
1✔
599
        rho_c = Constant(rho, name="rho")
1✔
600

601
        # Define time step size (this value is used only for step 1 if adaptive timestepping is used)
602
        # FIXME: change variable name to avoid confusion within dolfin adjoint
603
        self.dt = 0.1*self.dom.global_hmin/self.bd.HH_vel
1✔
604
        # self.dt = 0.05
605
        self.dt_c  = Constant(self.dt, name="dt_c")
1✔
606

607
        self.fprint("Viscosity: {:1.2e}".format(float(self.viscosity)))
1✔
608
        self.fprint("Density:   {:1.2e}".format(float(rho)))
1✔
609

610
        # Define trial and test functions for velocity
611
        u = TrialFunction(self.fs.V)
1✔
612
        v = TestFunction(self.fs.V)
1✔
613

614
        # Define trial and test functions for pressure
615
        p = TrialFunction(self.fs.Q)
1✔
616
        q = TestFunction(self.fs.Q)
1✔
617

618
        # Define functions for velocity solutions
619
        # >> _k = current (step k)
620
        # >> _k1 = previous (step k-1)
621
        # >> _k2 = double previous (step k-2)
622
        self.u_k = Function(self.fs.V, name="u_k")
1✔
623
        self.u_k1 = Function(self.fs.V, name="u_k1")
1✔
624
        self.u_k2 = Function(self.fs.V, name="u_k2")
1✔
625

626
        # Seed previous velocity fields with the chosen initial condition
627
        self.u_k.assign(self.bd.bc_velocity)
1✔
628
        self.u_k1.assign(self.bd.bc_velocity)
1✔
629
        self.u_k2.assign(self.bd.bc_velocity)
1✔
630

631
        # Calculate Reynolds stress 
632
        self.uk_sum = Function(self.fs.V, name="uk_sum")
1✔
633
        self.uk_sum.assign(self.dt_c*self.u_k)
1✔
634
        self.vertKE = Function(self.fs.Q, name="vertKE")
1✔
635

636
        # Define functions for pressure solutions
637
        # >> _k = current (step k)
638
        # >> _k1 = previous (step k-1)
639
        self.p_k  = Function(self.fs.Q, name="p_k")
1✔
640
        self.p_k1 = Function(self.fs.Q, name="p_k1")
1✔
641

642
        # Seed previous pressure fields with the chosen initial condition
643
        self.p_k1.assign(self.bd.bc_pressure)
1✔
644

645
        # ================================================================
646

647
        # Crank-Nicolson velocity
648
        U_CN  = 0.5*(u + self.u_k1)
1✔
649

650
        # Adams-Bashforth projected velocity
651
        U_AB = 1.5*self.u_k1 - 0.5*self.u_k2
1✔
652

653
        # ================================================================
654
        self.nu_T = self.ComputeTurbulenceModel(U_AB)
1✔
655

656
        # ================================================================
657

658
        # FIXME: This up_k function is only present to avoid errors  
659
        # during assignments in GenericSolver.__init__
660

661
        # Create the combined function space
662
        self.up_k = Function(self.fs.W, name="up_k")
1✔
663

664
        # Create the turbine force
665
        # FIXME: Should this be set by a numpy array operation or a fenics function?
666
        # self.tf = self.farm.TurbineForce(self.fs, self.dom.mesh, self.u_k2)
667
        # self.tf = Function(self.fs.V)
668

669
        self.tf = self.ComputeTurbineForce(self.u_k,inflow_angle,simTime=0.0,simTime_prev=None, dt=self.dt)
1✔
670
        self.u_k.assign(self.bd.bc_velocity)
1✔
671

672
        # Only the actuator lines point "upstream" against the flow
673
        # all other actuator forces need to be reversed to follow
674
        # the convention used in the unsteady problem
675
        # FIXME: We should be consistent about which way the turbine
676
        # forces are oriented.
677
        # if self.farm.turbine_method != "alm":
678
        #     self.tf *= -1.0
679

680
        # self.u_k2.vector()[:] = 0.0
681
        # self.u_k1.vector()[:] = 0.0
682

683
        # ================================================================
684

685
        # Define variational problem for step 1: tentative velocity
686
        # F1 = (1.0/self.dt_c)*inner(u - self.u_k1, v)*dx \
687
        #    + inner(dot(U_AB, nabla_grad(U_CN)), v)*dx \
688
        #    + (nu_c+self.nu_T)*inner(grad(U_CN), grad(v))*dx \
689
        #    + dot(nabla_grad(self.p_k1), v)*dx \
690
        #    - dot(-self.tf, v)*dx
691

692
        # F1 = (1.0/self.dt_c)*inner(u - self.u_k1, v)*dx \
693
        #    + inner(dot(U_AB, nabla_grad(U_CN)), v)*dx \
694
        #    + (nu_c+self.nu_T)*inner(grad(U_CN), grad(v))*dx \
695
        #    + dot(nabla_grad(self.p_k1), v)*dx \
696
        #    - dot(self.tf, v)*dx
697

698
        F1 = (1.0/self.dt_c)*inner(u - self.u_k1, v)*dx \
1✔
699
           + inner(dot(U_AB, nabla_grad(U_CN)), v)*dx \
700
           + (nu_c+self.nu_T)*inner(grad(U_CN), grad(v))*dx \
701
           + inner(grad(self.p_k1), v)*dx \
702
           - dot(self.tf, v)*dx
703

704
        self.a1 = lhs(F1)
1✔
705
        self.L1 = rhs(F1)
1✔
706

707
        # Define variational problem for step 2: pressure correction
708
        # self.a2 = dot(nabla_grad(p), nabla_grad(q))*dx
709
        # self.L2 = dot(nabla_grad(self.p_k1), nabla_grad(q))*dx - (1.0/self.dt_c)*div(self.u_k)*q*dx
710
        self.a2 = inner(grad(p), grad(q))*dx
1✔
711
        self.L2 = inner(grad(self.p_k1), grad(q))*dx - (1.0/self.dt_c)*div(self.u_k)*q*dx
1✔
712

713
        # phi = p - self.p_k
714
        # F2 = inner(grad(q), grad(phi))*dx - (1.0/self.dt_c)*div(u_k)*q*dx
715
        # self.a2 = lhs(F2)
716
        # self.L2 = rhs(F2)
717

718
        # Define variational problem for step 3: velocity update
719
        # self.a3 = dot(u, v)*dx
720
        # self.L3 = dot(self.u_k, v)*dx - self.dt_c*dot(nabla_grad(self.p_k - self.p_k1), v)*dx
721
        self.a3 = inner(u, v)*dx
1✔
722
        self.L3 = inner(self.u_k, v)*dx - self.dt_c*inner(grad(self.p_k - self.p_k1), v)*dx
1✔
723

724
        # F3 = inner(u, v)*dx - inner(self.u_k, v)*dx + self.dt_c*inner(phi, v)*dx
725
        # self.a3 = lhs(F3)
726
        # self.L3 = rhs(F3)
727

728

729
        # ================================================================
730

731
        self.fprint("Unsteady Problem Setup",special="footer")
1✔
732

733
# # ================================================================
734

735
#     def UpdateActuatorLineControls(self, c_lift = None, c_drag = None):
736

737
#         if c_lift is not None:
738
#             cl = np.array(c_lift, dtype = float)
739
#         if c_drag is not None:
740
#             cd = np.array(c_drag, dtype = float)
741

742
#         for k in range(self.num_actuator_nodes):
743
#             self.mcl[k] = Constant(cl[k])
744
#             self.mcd[k] = Constant(cd[k])
745

746
# # ================================================================
747

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc