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

NREL / WindSE / 9864676642

09 Jul 2024 09:51PM UTC coverage: 68.652% (+5.5%) from 63.193%
9864676642

Pull #118

github

web-flow
Merge 7ae709fa1 into b05183a0d
Pull Request #118: Patch fix

11 of 15 new or added lines in 4 files covered. (73.33%)

1 existing line in 1 file now uncovered.

5118 of 7455 relevant lines covered (68.65%)

0.69 hits per line

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

70.42
/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
            if self.farm.use_local_tf_dx:
1✔
83
                int_tf_x = 0
1✔
84
                int_tf_y = 0
1✔
85
                int_tf_z = 0
1✔
86
                for i in range(len(self.farm.tf_list)):
1✔
87
                    tf = self.farm.tf_list[i]
1✔
88
                    local_dx = self.farm.local_dx(i+1)
1✔
89

90
                    int_tf_x += assemble(inner(tf,e1)*local_dx)/self.dom.volume
1✔
91
                    int_tf_y += assemble(inner(tf,e2)*local_dx)/self.dom.volume
1✔
92
                    if self.dom.dim == 3:
1✔
93
                        int_tf_z += assemble(inner(tf,e3)*local_dx)/self.dom.volume
1✔
94
            else:
95
                tf = 0
1✔
96
                for i in range(len(self.farm.tf_list)):
1✔
97
                    tf += self.farm.tf_list[i]
1✔
98

99
                int_tf_x = assemble(inner(tf,e1)*dx)/self.dom.volume
1✔
100
                int_tf_y = assemble(inner(tf,e2)*dx)/self.dom.volume
1✔
101
                if self.dom.dim == 3:
1✔
102
                    int_tf_z = assemble(inner(tf,e3)*dx)/self.dom.volume
1✔
103

104
            self.tag_output("int_tf_x", int_tf_x)
1✔
105
            self.tag_output("int_tf_y", int_tf_y)
1✔
106

107
            if self.dom.dim == 3:
1✔
108
                self.tag_output("int_tf_z", int_tf_z)
1✔
109

110
                if self.farm.turbine_type == 'line':
1✔
111
                    chord = []
1✔
112
                    cl = []
1✔
113
                    cd = []
1✔
114
                    num_actuator_nodes = []
1✔
115
                    for i in range(self.farm.numturbs):
1✔
116
                        chord.append(self.farm.turbines[i].chord)
1✔
117
                        cl.append(self.farm.turbines[i].cl)
1✔
118
                        cd.append(self.farm.turbines[i].cd)
1✔
119
                        num_actuator_nodes.append(self.farm.turbines[i].num_actuator_nodes)
1✔
120

121
                    self.tag_output("min_chord", np.min(chord))
1✔
122
                    self.tag_output("max_chord", np.max(chord))
1✔
123
                    self.tag_output("avg_chord", np.mean(chord))
1✔
124
                    self.tag_output("min_cl", np.min(cl))
1✔
125
                    self.tag_output("max_cl", np.max(cl))
1✔
126
                    self.tag_output("avg_cl", np.mean(cl))
1✔
127
                    self.tag_output("min_cd", np.min(cd))
1✔
128
                    self.tag_output("max_cd", np.max(cd))
1✔
129
                    self.tag_output("avg_cd", np.mean(cd))
1✔
130
                    self.tag_output("num_actuator_nodes", np.mean(num_actuator_nodes))
1✔
131
                
132

133
    def ComputeTurbineForceTerm(self,u,v,inflow_angle,**kwargs):
1✔
134
        tf_start = time.time()
1✔
135
        self.fprint("Calculating Turbine Force",special="header")
1✔
136

137
        # Compute the relative yaw angle
138
        if inflow_angle is None:
1✔
139
            inflow_angle = self.dom.inflow_angle
×
140

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

143
        # Create a list of turbine force function and domain of integration for each turbine
144
        if self.farm.turbine_type == "disabled" or self.farm.numturbs == 0:
1✔
145

146
            # initialize turbine force
NEW
147
            self.farm.compute_turbine_force(u,v,inflow_angle,self.fs,**kwargs)
×
148

149
            # but set the turbine force term to zero
NEW
150
            v, q = TestFunctions(self.fs.W)
×
NEW
151
            tf_term = inner(Function(self.fs.V), v)*dx
×
152

153
        else:
154

155
            # compute tf and dx for each turbine
156
            tf_term = self.farm.compute_turbine_force(u,v,inflow_angle,self.fs,**kwargs)
1✔
157

158
        tf_stop = time.time()
1✔
159
        self.fprint("Turbine Force Calculated: {:1.2f} s".format(tf_stop-tf_start),special="footer")
1✔
160
        return tf_term
1✔
161

162
    def ComputeTurbulenceModel(self, u):
1✔
163
        self.fprint(f"Using Turbulence Model: {self.turbulence_model}")
1✔
164
        if self.turbulence_model is not None:
1✔
165
            # Calculate eddy viscosity, if using
166
            # self.turbulence_model = 'smagorinsky'
167
            # self.turbulence_model = 'mixing_length'
168
            # self.turbulence_model = None
169

170
            # Strain rate tensor, 0.5*(du_i/dx_j + du_j/dx_i)
171
            Sij = sym(grad(u))
1✔
172

173
            # sqrt(Sij*Sij)
174
            strainMag = (2.0*inner(Sij, Sij))**0.5
1✔
175

176
            if self.turbulence_model == 'smagorinsky':
1✔
177
                # Smagorinsky constant, typically around 0.17
178
                Cs = 0.17
1✔
179

180
                # Define filter scale (all models use this)
181
                filter_scale = CellVolume(self.dom.mesh)**(1.0/self.dom.dim)
1✔
182

183
                # Eddy viscosity
184
                nu_T = Cs**2 * filter_scale**2 * strainMag
1✔
185

186
            elif self.turbulence_model == 'mixing_length':
1✔
187
                # von Karman constant
188
                vonKarman = 0.41
1✔
189

190
                if self.dom.dim == 3:
1✔
191
                    depth = self.bd.depth.vector()[:]
1✔
192
                else:
193
                    if self.farm.numturbs == 0:
1✔
194
                        depth = self.params["boundary_conditions"]["vel_height"]
×
195
                    else:
196
                        depth = self.farm.turbines[0].HH
1✔
197

198
                numer = vonKarman*depth
1✔
199
                denom = 1.0 + vonKarman*depth/self.lmax
1✔
200

201
                l_mix = Function(self.fs.Q)
1✔
202
                l_mix.vector()[:] = numer/denom
1✔
203
                l_mix.rename("l_mix","l_mix")
1✔
204

205
                # Eddy viscosity
206
                nu_T = l_mix**2 * strainMag
1✔
207

208
        else:
209
            nu_T = Constant(0)
×
210

211
        return nu_T
1✔
212

213

214
    ############################################
215
    ############################################
216
    ### this is a hack to accommodate the fact that
217
    ### alm values are not stored in the wind_farm
218
    ### object. eventually we might want to move
219
    ### it there.
220
    ############################################
221
    ############################################
222
    def CopyALMtoWindFarm(self):
1✔
223
        self.farm.mcl = self.mcl
×
224
        self.farm.mcd = self.mcd
×
225
        self.farm.mchord = self.mchord
×
226
        self.farm.cl = self.cl
×
227
        self.farm.cd = self.cd
×
228
        self.farm.chord = self.chord
×
229
        self.farm.num_actuator_nodes = self.num_actuator_nodes
×
230

231
    def SimpleControlUpdate(self):
1✔
232
        self.u_k, self.p_k = split(self.up_k)
×
233
        self.farm.SimpleControlUpdate()
×
234

235

236
    def ChangeWindAngle(self,inflow_angle):
1✔
237
        """
238
        This function recomputes all necessary components for a new wind direction
239

240
        Args: 
241
            inflow_angle (float): The new wind angle in radians
242
        """
243
        adj_start = time.time()
1✔
244
        self.fprint("Adjusting Wind Angle",special="header")
1✔
245
        self.fprint("New Angle: {:1.8f} rads".format(inflow_angle))
1✔
246
        self.dom.RecomputeBoundaryMarkers(inflow_angle)
1✔
247
        self.bd.RecomputeVelocity(inflow_angle)
1✔
248
        self.ComputeFunctional(inflow_angle)
1✔
249

250
        adj_stop = time.time()
1✔
251
        self.fprint("Wind Angle Adjusted: {:1.2f} s".format(adj_stop-adj_start),special="footer")
1✔
252

253
        # self.tf.assign(tf_temp)
254

255
    def ChangeWindSpeed(self,inflow_speed):
1✔
256
        adj_start = time.time()
×
257
        self.fprint("Adjusting Wind Speed",special="header")
×
258
        self.fprint("New Speed: {:1.8f} m/s".format(inflow_speed/self.dom.xscale))
×
259
        self.bd.HH_vel = inflow_speed
×
260
        adj_stop = time.time()
×
261
        self.fprint("Wind Speed Adjusted: {:1.2f} s".format(adj_stop-adj_start),special="footer")
×
262

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

265
        if c_lift is not None:
×
266
            cl = np.array(c_lift, dtype = float)
×
267
            self.cl[turb_i] = cl
×
268
            for k in range(self.num_actuator_nodes):
×
269
                self.mcl[turb_i][k] = Constant(cl[k])
×
270
        if c_drag is not None:
×
271
            cd = np.array(c_drag, dtype = float)
×
272
            self.cd[turb_i] = cd
×
273
            for k in range(self.num_actuator_nodes):
×
274
                self.mcd[turb_i][k] = Constant(cd[k])
×
275
        if chord is not None:
×
276
            chord = np.array(chord, dtype = float)
×
277
            self.chord[turb_i] = chord
×
278
            for k in range(self.num_actuator_nodes):
×
279
                self.mchord[turb_i][k] = Constant(chord[k])
×
280
        if yaw is not None:
×
281
            yaw = float(yaw)
×
282
            self.farm.yaw[turb_i] = yaw
×
283
            self.farm.myaw[turb_i] = Constant(yaw)
×
284
        
285

286
        self.CopyALMtoWindFarm()
×
287

288

289
class StabilizedProblem(GenericProblem):
1✔
290
    """
291
    The StabilizedProblem setup everything required for solving Navier-Stokes with 
292
    a stabilization term
293

294
    Args: 
295
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
296
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
297
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
298
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
299
    """
300
    def __init__(self,domain,windfarm,function_space,boundary_conditions):
1✔
301
        super(StabilizedProblem, self).__init__(domain,windfarm,function_space,boundary_conditions)
1✔
302
        
303
        ### Create Functional ###
304
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
305
        self.DebugOutput()
1✔
306

307

308
    def ComputeFunctional(self,inflow_angle):
1✔
309
        self.fprint("Setting Up Stabilized Problem",special="header")
1✔
310

311
        ### Create the test/trial/functions ###
312
        self.up_k = Function(self.fs.W)
1✔
313
        self.u_k,self.p_k = split(self.up_k)
1✔
314
        v,q = TestFunctions(self.fs.W)
1✔
315

316
        ### Set the x scaling ###
317
        Sx = self.dom.xscale
1✔
318

319
        ### Set the initial guess ###
320
        u0 = self.bd.u0
1✔
321
        self.up_k.assign(u0)
1✔
322

323
        # mem0=memory_usage()[0]
324
        # mem_out, self.tf = memory_usage((self.ComputeTurbineForce,(self.u_k,inflow_angle),{}),max_usage=True,retval=True,max_iterations=1)
325
        # self.fprint("Memory Used:  {:1.2f} MB".format(mem_out-mem0))
326
        tf_term = self.ComputeTurbineForceTerm(self.u_k,v,inflow_angle)
1✔
327

328
        # set_log_level(LogLevel.DEBUG)
329
        # tic = time.time()
330
        # assemble(turbine_force_term)
331
        # toc = time.time()
332
        # print(f"assemble time: {toc-tic} s")
333
        # exit()
334

335

336
        ### These constants will be moved into the params file ###
337
        f = Constant((0.0,)*self.dom.dim)
1✔
338
        f.rename("f","f")
1✔
339
        
340
        nu = self.viscosity
1✔
341
        vonKarman=0.41
1✔
342
        eps=Constant(self.stability_eps)
1✔
343
        eps.rename("eps","eps")
1✔
344

345
        self.fprint("Viscosity:                 {:1.2e}".format(float(self.viscosity)))
1✔
346
        self.fprint("Max Mixing Length:         {:1.2e}".format(float(self.lmax)))
1✔
347
        self.fprint("Stabilization Coefficient: {:1.2e}".format(float(eps)))
1✔
348

349
        ### Calculate nu_T
350
        self.nu_T=self.ComputeTurbulenceModel(self.u_k)
1✔
351
        # self.nu_T=Constant(0.0)
352
        self.ReyStress=self.nu_T*grad(self.u_k)
1✔
353
        self.vertKE= self.ReyStress[0,2]*self.u_k[0]
1✔
354

355
        ### Create the functional ###
356
        self.F = inner(grad(self.u_k)*self.u_k, v)*dx
1✔
357
        self.F +=   Sx*Sx*(nu+self.nu_T)*inner(grad(self.u_k), grad(v))*dx
1✔
358
        self.F += - inner(div(v),self.p_k)*dx
1✔
359
        self.F += - inner(div(self.u_k),q)*dx
1✔
360
        self.F += - inner(f,v)*dx
1✔
361
        self.F += - tf_term 
1✔
362

363

364
        # Add body force to functional
365
        if abs(float(self.mbody_force)) >= 1e-14:
1✔
366
            self.fprint("Using Body Force")
×
367
            self.F += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
368

369
        ################ THIS IS A CHEAT ####################\
370
        if self.use_corrective_force:
1✔
371
            self.fprint("Using Corrective Force")
×
372
            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)))
×
373
            extra_l_mix = Function(self.fs.Q)
×
374
            extra_l_mix.vector()[:] = np.divide(vonKarman*self.bd.depth.vector()[:]/Sx,(1.+np.divide(vonKarman*self.bd.depth.vector()[:]/Sx,self.lmax)))
×
375
            extra_nu_T = extra_l_mix**2.*extra_S
×
376
            extra_DP =dot(self.bd.u0,grad(self.bd.u0)) - div((nu+extra_nu_T)*grad(self.bd.bc_velocity))
×
377

378
            self.F += inner(extra_DP,v)*dx
×
379
        ########################################################
380

381
        # 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
382
        # 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 
383
        # stab_sans_tf = - eps*inner(grad(q), grad(self.p_k))*dx 
384
        # self.F_sans_tf += stab
385

386
        ### Add in the Stabilizing term ###
387
        if abs(float(eps)) >= 1e-14:
1✔
388
            self.fprint("Using Stabilization Term")
1✔
389
            stab = - eps*inner(grad(q), grad(self.p_k))*dx - eps*inner(grad(q), dot(grad(self.u_k), self.u_k))*dx 
1✔
390
            self.F += stab
1✔
391

392

393
        if self.use_25d_model and self.dom.dim == 2 :
1✔
394
            if self.dom.dim == 3:
×
395
                raise ValueError("The 2.5D model requires a 2D simulation.")
×
396

397
            self.fprint("Using 2.5D model")
×
398
            dudx = Dx(self.u_next[0], 0)
×
399
            dvdy = Dx(self.u_next[1], 1)
×
400

401
            if inflow_angle is None:
×
402
                term25 = dvdy*q*dx
×
403
            else:
404
                term25 = (abs(sin(inflow_angle))*dudx*q + abs(cos(inflow_angle))*dvdy*q)*dx
×
405

406
            self.F -= term25
×
407

408
        self.fprint("Stabilized Problem Setup",special="footer")
1✔
409

410

411
class TaylorHoodProblem(GenericProblem):
1✔
412
    """
413
    The TaylorHoodProblem sets up everything required for solving Navier-Stokes 
414

415
    Args: 
416
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
417
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
418
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
419
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
420
    """
421
    def __init__(self,domain,windfarm,function_space,boundary_conditions):
1✔
422
        super(TaylorHoodProblem, self).__init__(domain,windfarm,function_space,boundary_conditions)
1✔
423

424

425
        self.first_loop = True
1✔
426

427
        ### Create Functional ###
428
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
429
        self.DebugOutput()
1✔
430

431
    def ComputeFunctional(self,inflow_angle):
1✔
432
        self.fprint("Setting Up Taylor-Hood Problem",special="header")
1✔
433

434
        ### These constants will be moved into the params file ###
435
        f = Constant((0.0,)*self.dom.dim)
1✔
436
        vonKarman=0.41
1✔
437
        eps=Constant(0.000001)
1✔
438
        nu = self.viscosity
1✔
439

440

441
        self.fprint("Viscosity:         {:1.2e}".format(float(self.viscosity)))
1✔
442
        self.fprint("Max Mixing Length: {:1.2e}".format(float(self.lmax)))
1✔
443

444

445
        ### Create the test/trial/functions ###
446
        self.v,self.q = TestFunctions(self.fs.W)
1✔
447

448
        ### Set the initial guess ###
449
        ### (this will become a separate function.)
450
        # if self.first_loop:
451
        #     self.up_k = Function(self.fs.W)
452
        #     self.up_k.assign(self.bd.u0)
453
        #     self.first_loop = False
454
        # else:
455
        #     temp_up = self.up_k.copy()
456
        self.up_k = Function(self.fs.W)
1✔
457
        self.up_k.assign(self.bd.u0)
1✔
458
        self.u_k,self.p_k = split(self.up_k)
1✔
459

460
        ### Calculate nu_T
461
        self.nu_T=self.ComputeTurbulenceModel(self.u_k)
1✔
462

463
        ### Create the turbine force ###
464
        tf_term = self.ComputeTurbineForceTerm(self.u_k,self.v,inflow_angle)
1✔
465

466
        ### Create the functional ###
467
        self.F = inner(grad(self.u_k)*self.u_k, self.v)*dx
1✔
468
        self.F +=   (nu+self.nu_T)*inner(grad(self.u_k), grad(self.v))*dx
1✔
469
        self.F += - inner(div(self.v),self.p_k)*dx
1✔
470
        self.F += - inner(div(self.u_k),self.q)*dx
1✔
471
        # self.F += - inner(f,v)*dx
472
        self.F += - tf_term 
1✔
473

474
        # ### Add in the Stabilizing term ###
475
        # if abs(float(eps)) >= 1e-14:
476
        #     self.fprint("Using Stabilization Term")
477
        #     stab = - eps*inner(grad(q), grad(self.p_k))*dx - eps*inner(grad(q), dot(grad(self.u_k), self.u_k))*dx 
478
        #     self.F += stab
479

480
        # Add body force to functional
481
        if abs(float(self.mbody_force)) >= 1e-14:
1✔
482
            self.fprint("Using Body Force")
×
483
            self.F += inner(-self.mbody_force*self.bd.inflow_unit_vector,self.v)*dx
×
484

485
        if self.use_25d_model:
1✔
486
            if self.dom.dim == 3:
1✔
487
                raise ValueError("The 2.5D model requires a 2D simulation.")
×
488

489
            self.fprint("Using 2.5D model")
1✔
490
            dudx = Dx(self.u_k[0], 0)
1✔
491
            dvdy = Dx(self.u_k[1], 1)
1✔
492

493
            if inflow_angle is None:
1✔
494
                term25 = dvdy*self.q*dx
×
495
            else:
496
                term25 = (abs(sin(inflow_angle))*dudx*self.q + abs(cos(inflow_angle))*dvdy*self.q)*dx
1✔
497

498
            self.F -= term25
1✔
499

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

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

504
class IterativeSteady(GenericProblem):
1✔
505
    """
506
    The IterativeSteady sets up everything required for solving Navier-Stokes using
507
    the SIMPLE algorithm
508

509
    Args: 
510
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
511
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
512
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
513
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
514
    """
515
    def __init__(self, domain, windfarm, function_space, boundary_conditions):
1✔
516
        super(IterativeSteady, self).__init__(domain, windfarm, function_space, boundary_conditions)
×
517
        self.fprint("Setting Up *Iterative* Steady Problem", special="header")
×
518

519
        ### Create Functional ###
520
        self.ComputeFunctional(self.dom.inflow_angle)
×
521

522
    def ComputeFunctional(self,inflow_angle):
1✔
523
        # ================================================================
524

525
        # Define fluid properties
526
        # FIXME: These should probably be set in params.yaml input filt
527
        # nu = 1/10000
528
        nu = Constant(self.viscosity)
×
529

530
        # Trial functions for velocity and pressure
531
        u = TrialFunction(self.fs.V)
×
532
        p = TrialFunction(self.fs.Q)
×
533

534
        # Test functions for velocity and pressure
535
        v = TestFunction(self.fs.V)
×
536
        q = TestFunction(self.fs.Q)
×
537

538
        ### Define Velocity Functions ###
539
        self.u_k = Function(self.fs.V, name="u_k")
×
540
        self.u_hat = Function(self.fs.V)
×
541
        self.u_s = Function(self.fs.V)
×
542
        self.du = Function(self.fs.V)
×
543
        self.u_k_old = Function(self.fs.V)
×
544

545
        ### Define Pressure Functions ###
546
        self.p_k = Function(self.fs.Q, name="p_k")
×
547
        self.p_s = Function(self.fs.Q)
×
548
        self.dp = Function(self.fs.Q)
×
549
        self.p_k_old = Function(self.fs.Q)
×
550

551
        U_CN = 0.5*(u + self.u_k) 
×
552

553
        # Adams-Bashforth velocity
554
        # U_AB = 1.5*u_k - 0.5*u_k_old # Time level k+1/2
555
        U_AB = 2.0*self.u_k - 1.0*self.u_k_old # Time level k+1
×
556

557
        # Compute eddy viscosity
558
        self.nu_T=self.ComputeTurbulenceModel(U_AB)
×
559

560
        # ================================================================
561

562
        # FIXME: This up_k function is only present to avoid errors  
563
        # during assignments in GenericSolver.__init__
564

565
        # Create the combined function space
566
        self.up_k = Function(self.fs.W)
×
567

568
        # Create the turbine force
569
        # FIXME: Should this be set by a numpy array operation or a fenics function?
570
        # self.tf = self.farm.TurbineForce(self.fs, self.dom.mesh, self.u_k2)
571
        # self.tf = Function(self.fs.V)
572

573
        tf_term = self.ComputeTurbineForceTerm(self.u_k,v,inflow_angle)
×
574
        # self.u_k.assign(self.bd.bc_velocity)
575

576
        # self.u_k2.vector()[:] = 0.0
577
        # self.u_k1.vector()[:] = 0.0
578

579
        # ================================================================
580

581
        # Solve for u_hat, a velocity estimate which doesn't include pressure gradient effects
582
        F1 = inner(dot(self.u_k, nabla_grad(u)), v)*dx \
×
583
           + (nu+self.nu_T)*inner(grad(u), grad(v))*dx \
584
           - tf_term 
585

586
        # Add body force to functional
587
        F1 += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
588

589
        self.F1_lhs = lhs(F1)
×
590
        self.F1_rhs = rhs(F1)
×
591

592

593
        # Use u_hat to solve for the pressure field
594
        self.F2_lhs = inner(grad(p), grad(q))*dx
×
595
        self.F2_rhs = - div(self.u_hat)*q*dx
×
596

597
        self.dt_1 = Constant(1) # Becomes (1/dt, 0) for (unsteady, steady) state
×
598
        self.dt_2 = Constant(1) # Becomes (1/dt, 1) for (unsteady, steady) state
×
599
        self.dt_3 = Constant(1) # Becomes (  dt, 1) for (unsteady, steady) state
×
600

601
        # Solve for u_star, a predicted velocity which includes the pressure gradient
602
        F3 = inner(dot(self.u_k, nabla_grad(u)), v)*dx \
×
603
           + (nu+self.nu_T)*inner(grad(u), grad(v))*dx \
604
           - tf_term \
605
           + inner(grad(self.p_k), v)*dx \
606
           + self.dt_1*inner(u - self.u_k, v)*dx
607

608
        # Add body force to functional
609
        F3 += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
610

611
        self.F3_lhs = lhs(F3)
×
612
        self.F3_rhs = rhs(F3)
×
613

614

615
        # Define variational problem for step 2: pressure correction
616
        self.F4_lhs = inner(grad(p), grad(q))*dx
×
617
        self.F4_rhs = - self.dt_2*div(self.u_s)*q*dx
×
618

619

620
        # Define variational problem for step 3: velocity update
621
        self.F5_lhs = inner(u, v)*dx
×
622
        self.F5_rhs = - self.dt_3*inner(grad(self.dp), v)*dx
×
623

624
        # ================================================================
625

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

628
# ================================================================
629

630
class UnsteadyProblem(GenericProblem):
1✔
631
    """
632
    The UnsteadyProblem sets up everything required for solving Navier-Stokes using
633
    a fractional-step method with an adaptive timestep size
634

635
    Args: 
636
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
637
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
638
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
639
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
640
    """
641
    def __init__(self, domain, windfarm, function_space, boundary_conditions):
1✔
642
        super(UnsteadyProblem, self).__init__(domain, windfarm, function_space, boundary_conditions)
1✔
643
        self.fprint("Setting Up Unsteady Problem", special="header")
1✔
644

645
        ### Create Functional ###
646
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
647
        self.DebugOutput()
1✔
648

649
    def ComputeFunctional(self,inflow_angle):
1✔
650
        # ================================================================
651

652
        # Define fluid properties
653
        # FIXME: These should probably be set in params.yaml input filt
654
        # nu = 1/10000
655
        rho = 1
1✔
656
        nu_c = Constant(self.viscosity, name="viscosity")
1✔
657
        rho_c = Constant(rho, name="rho")
1✔
658

659
        # Define time step size (this value is used only for step 1 if adaptive timestepping is used)
660
        # FIXME: change variable name to avoid confusion within dolfin adjoint
661
        self.dt = 0.1*self.dom.global_hmin/self.bd.HH_vel
1✔
662
        # self.dt = 0.05
663
        self.dt_c  = Constant(self.dt, name="dt_c")
1✔
664

665
        self.fprint("Viscosity: {:1.2e}".format(float(self.viscosity)))
1✔
666
        self.fprint("Density:   {:1.2e}".format(float(rho)))
1✔
667

668
        # Define trial and test functions for velocity
669
        u = TrialFunction(self.fs.V)
1✔
670
        v = TestFunction(self.fs.V)
1✔
671

672
        # Define trial and test functions for pressure
673
        p = TrialFunction(self.fs.Q)
1✔
674
        q = TestFunction(self.fs.Q)
1✔
675

676
        # Define functions for velocity solutions
677
        # >> _k = current (step k)
678
        # >> _k1 = previous (step k-1)
679
        # >> _k2 = double previous (step k-2)
680
        self.u_k = Function(self.fs.V, name="u_k")
1✔
681
        self.u_k1 = Function(self.fs.V, name="u_k1")
1✔
682
        self.u_k2 = Function(self.fs.V, name="u_k2")
1✔
683

684
        # Seed previous velocity fields with the chosen initial condition
685
        self.u_k.assign(self.bd.bc_velocity)
1✔
686
        self.u_k1.assign(self.bd.bc_velocity)
1✔
687
        self.u_k2.assign(self.bd.bc_velocity)
1✔
688

689
        # Calculate Reynolds stress 
690
        self.uk_sum = Function(self.fs.V, name="uk_sum")
1✔
691
        self.uk_sum.assign(self.dt_c*self.u_k)
1✔
692
        self.vertKE = Function(self.fs.Q, name="vertKE")
1✔
693

694
        # Define functions for pressure solutions
695
        # >> _k = current (step k)
696
        # >> _k1 = previous (step k-1)
697
        self.p_k  = Function(self.fs.Q, name="p_k")
1✔
698
        self.p_k1 = Function(self.fs.Q, name="p_k1")
1✔
699

700
        # Seed previous pressure fields with the chosen initial condition
701
        self.p_k1.assign(self.bd.bc_pressure)
1✔
702

703
        # ================================================================
704

705
        # Crank-Nicolson velocity
706
        U_CN  = 0.5*(u + self.u_k1)
1✔
707

708
        # Adams-Bashforth projected velocity
709
        U_AB = 1.5*self.u_k1 - 0.5*self.u_k2
1✔
710

711
        # ================================================================
712
        self.nu_T = self.ComputeTurbulenceModel(U_AB)
1✔
713

714
        # ================================================================
715

716
        # FIXME: This up_k function is only present to avoid errors  
717
        # during assignments in GenericSolver.__init__
718

719
        # Create the combined function space
720
        self.up_k = Function(self.fs.W, name="up_k")
1✔
721

722
        # Create the turbine force
723
        # FIXME: Should this be set by a numpy array operation or a fenics function?
724
        # self.tf = self.farm.TurbineForce(self.fs, self.dom.mesh, self.u_k2)
725
        # self.tf = Function(self.fs.V)
726

727
        tf_term = self.ComputeTurbineForceTerm(self.u_k,v,inflow_angle,simTime=0.0,simTime_prev=None, dt=self.dt)
1✔
728

729
        self.u_k.assign(self.bd.bc_velocity)
1✔
730

731
        # Only the actuator lines point "upstream" against the flow
732
        # all other actuator forces need to be reversed to follow
733
        # the convention used in the unsteady problem
734
        # FIXME: We should be consistent about which way the turbine
735
        # forces are oriented.
736
        # if self.farm.turbine_method != "alm":
737
        #     self.tf *= -1.0
738

739
        # self.u_k2.vector()[:] = 0.0
740
        # self.u_k1.vector()[:] = 0.0
741

742
        # ================================================================
743

744
        # Define variational problem for step 1: tentative velocity
745
        # F1 = (1.0/self.dt_c)*inner(u - self.u_k1, v)*dx \
746
        #    + inner(dot(U_AB, nabla_grad(U_CN)), v)*dx \
747
        #    + (nu_c+self.nu_T)*inner(grad(U_CN), grad(v))*dx \
748
        #    + dot(nabla_grad(self.p_k1), v)*dx \
749
        #    - dot(-self.tf, v)*dx
750

751
        # F1 = (1.0/self.dt_c)*inner(u - self.u_k1, v)*dx \
752
        #    + inner(dot(U_AB, nabla_grad(U_CN)), v)*dx \
753
        #    + (nu_c+self.nu_T)*inner(grad(U_CN), grad(v))*dx \
754
        #    + dot(nabla_grad(self.p_k1), v)*dx \
755
        #    - dot(self.tf, v)*dx
756

757
        F1 = (1.0/self.dt_c)*inner(u - self.u_k1, v)*dx \
1✔
758
           + inner(dot(U_AB, nabla_grad(U_CN)), v)*dx \
759
           + (nu_c+self.nu_T)*inner(grad(U_CN), grad(v))*dx \
760
           + inner(grad(self.p_k1), v)*dx \
761
           - tf_term
762

763
        self.a1 = lhs(F1)
1✔
764
        self.L1 = rhs(F1)
1✔
765

766
        # Define variational problem for step 2: pressure correction
767
        # self.a2 = dot(nabla_grad(p), nabla_grad(q))*dx
768
        # self.L2 = dot(nabla_grad(self.p_k1), nabla_grad(q))*dx - (1.0/self.dt_c)*div(self.u_k)*q*dx
769
        self.a2 = inner(grad(p), grad(q))*dx
1✔
770
        self.L2 = inner(grad(self.p_k1), grad(q))*dx - (1.0/self.dt_c)*div(self.u_k)*q*dx
1✔
771

772
        # phi = p - self.p_k
773
        # F2 = inner(grad(q), grad(phi))*dx - (1.0/self.dt_c)*div(u_k)*q*dx
774
        # self.a2 = lhs(F2)
775
        # self.L2 = rhs(F2)
776

777
        # Define variational problem for step 3: velocity update
778
        # self.a3 = dot(u, v)*dx
779
        # self.L3 = dot(self.u_k, v)*dx - self.dt_c*dot(nabla_grad(self.p_k - self.p_k1), v)*dx
780
        self.a3 = inner(u, v)*dx
1✔
781
        self.L3 = inner(self.u_k, v)*dx - self.dt_c*inner(grad(self.p_k - self.p_k1), v)*dx
1✔
782

783
        # F3 = inner(u, v)*dx - inner(self.u_k, v)*dx + self.dt_c*inner(phi, v)*dx
784
        # self.a3 = lhs(F3)
785
        # self.L3 = rhs(F3)
786

787

788
        # ================================================================
789

790
        self.fprint("Unsteady Problem Setup",special="footer")
1✔
791

792
# # ================================================================
793

794
#     def UpdateActuatorLineControls(self, c_lift = None, c_drag = None):
795

796
#         if c_lift is not None:
797
#             cl = np.array(c_lift, dtype = float)
798
#         if c_drag is not None:
799
#             cd = np.array(c_drag, dtype = float)
800

801
#         for k in range(self.num_actuator_nodes):
802
#             self.mcl[k] = Constant(cl[k])
803
#             self.mcd[k] = Constant(cd[k])
804

805
# # ================================================================
806

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

© 2026 Coveralls, Inc