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

NREL / WindSE / 9522960252

14 Jun 2024 10:12PM UTC coverage: 66.835% (+3.6%) from 63.193%
9522960252

Pull #115

github

web-flow
Merge ced35bf90 into b05183a0d
Pull Request #115: Adding powercurve actuator method

70 of 301 new or added lines in 9 files covered. (23.26%)

1 existing line in 1 file now uncovered.

5155 of 7713 relevant lines covered (66.84%)

0.67 hits per line

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

70.62
/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
            # if there are no turbine return an zero force term
NEW
147
            v, q = TestFunctions(self.fs.W)
×
NEW
148
            tf_term = inner(Function(self.fs.V), v)*dx
×
149
        else:
150

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

154
        tf_stop = time.time()
1✔
155
        self.fprint("Turbine Force Calculated: {:1.2f} s".format(tf_stop-tf_start),special="footer")
1✔
156
        return tf_term
1✔
157

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

166
            # Strain rate tensor, 0.5*(du_i/dx_j + du_j/dx_i)
167
            Sij = sym(grad(u))
1✔
168

169
            # sqrt(Sij*Sij)
170
            strainMag = (2.0*inner(Sij, Sij))**0.5
1✔
171

172
            if self.turbulence_model == 'smagorinsky':
1✔
173
                # Smagorinsky constant, typically around 0.17
174
                Cs = 0.17
1✔
175

176
                # Define filter scale (all models use this)
177
                filter_scale = CellVolume(self.dom.mesh)**(1.0/self.dom.dim)
1✔
178

179
                # Eddy viscosity
180
                nu_T = Cs**2 * filter_scale**2 * strainMag
1✔
181

182
            elif self.turbulence_model == 'mixing_length':
1✔
183
                # von Karman constant
184
                vonKarman = 0.41
1✔
185

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

194
                numer = vonKarman*depth
1✔
195
                denom = 1.0 + vonKarman*depth/self.lmax
1✔
196

197
                l_mix = Function(self.fs.Q)
1✔
198
                l_mix.vector()[:] = numer/denom
1✔
199
                l_mix.rename("l_mix","l_mix")
1✔
200

201
                # Eddy viscosity
202
                nu_T = l_mix**2 * strainMag
1✔
203

204
        else:
205
            nu_T = Constant(0)
×
206

207
        return nu_T
1✔
208

209

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

227
    def SimpleControlUpdate(self):
1✔
228
        self.u_k, self.p_k = split(self.up_k)
×
229
        self.farm.SimpleControlUpdate()
×
230

231

232
    def ChangeWindAngle(self,inflow_angle):
1✔
233
        """
234
        This function recomputes all necessary components for a new wind direction
235

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

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

249
        # self.tf.assign(tf_temp)
250

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

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

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

281

282
        self.CopyALMtoWindFarm()
×
283

284

285
class StabilizedProblem(GenericProblem):
1✔
286
    """
287
    The StabilizedProblem setup everything required for solving Navier-Stokes with
288
    a stabilization term
289

290
    Args:
291
        domain (:meth:`windse.DomainManager.GenericDomain`): a windse domain object.
292
        windfarm (:meth:`windse.WindFarmManager.GenericWindFarmm`): a windse windfarm object.
293
        function_space (:meth:`windse.FunctionSpaceManager.GenericFunctionSpace`): a windse function space object.
294
        boundary_conditions (:meth:`windse.BoundaryManager.GenericBoundary`): a windse boundary object.
295
    """
296
    def __init__(self,domain,windfarm,function_space,boundary_conditions):
1✔
297
        super(StabilizedProblem, self).__init__(domain,windfarm,function_space,boundary_conditions)
1✔
298

299
        ### Create Functional ###
300
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
301
        self.DebugOutput()
1✔
302

303

304
    def ComputeFunctional(self,inflow_angle):
1✔
305
        self.fprint("Setting Up Stabilized Problem",special="header")
1✔
306

307
        ### Create the test/trial/functions ###
308
        self.up_k = Function(self.fs.W)
1✔
309
        self.u_k,self.p_k = split(self.up_k)
1✔
310
        v,q = TestFunctions(self.fs.W)
1✔
311

312
        ### Set the x scaling ###
313
        Sx = self.dom.xscale
1✔
314

315
        ### Set the initial guess ###
316
        u0 = self.bd.u0
1✔
317
        self.up_k.assign(u0)
1✔
318

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

324
        # set_log_level(LogLevel.DEBUG)
325
        # tic = time.time()
326
        # assemble(turbine_force_term)
327
        # toc = time.time()
328
        # print(f"assemble time: {toc-tic} s")
329
        # exit()
330

331

332
        ### These constants will be moved into the params file ###
333
        f = Constant((0.0,)*self.dom.dim)
1✔
334
        f.rename("f","f")
1✔
335

336
        nu = self.viscosity
1✔
337
        vonKarman=0.41
1✔
338
        eps=Constant(self.stability_eps)
1✔
339
        eps.rename("eps","eps")
1✔
340

341
        self.fprint("Viscosity:                 {:1.2e}".format(float(self.viscosity)))
1✔
342
        self.fprint("Max Mixing Length:         {:1.2e}".format(float(self.lmax)))
1✔
343
        self.fprint("Stabilization Coefficient: {:1.2e}".format(float(eps)))
1✔
344

345
        ### Calculate nu_T
346
        self.nu_T=self.ComputeTurbulenceModel(self.u_k)
1✔
347
        # self.nu_T=Constant(0.0)
348
        self.ReyStress=self.nu_T*grad(self.u_k)
1✔
349
        self.vertKE= self.ReyStress[0,2]*self.u_k[0]
1✔
350

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

359

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

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

374
            self.F += inner(extra_DP,v)*dx
×
375
        ########################################################
376

377
        # 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
378
        # 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
379
        # stab_sans_tf = - eps*inner(grad(q), grad(self.p_k))*dx
380
        # self.F_sans_tf += stab
381

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

388

389
        if self.use_25d_model and self.dom.dim == 2 :
1✔
390
            if self.dom.dim == 3:
×
391
                raise ValueError("The 2.5D model requires a 2D simulation.")
×
392

393
            self.fprint("Using 2.5D model")
×
394
            dudx = Dx(self.u_next[0], 0)
×
395
            dvdy = Dx(self.u_next[1], 1)
×
396

397
            if inflow_angle is None:
×
398
                term25 = dvdy*q*dx
×
399
            else:
400
                term25 = (abs(sin(inflow_angle))*dudx*q + abs(cos(inflow_angle))*dvdy*q)*dx
×
401

402
            self.F -= term25
×
403

404
        self.fprint("Stabilized Problem Setup",special="footer")
1✔
405

406

407
class TaylorHoodProblem(GenericProblem):
1✔
408
    """
409
    The TaylorHoodProblem sets up everything required for solving Navier-Stokes
410

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

420

421
        self.first_loop = True
1✔
422

423
        ### Create Functional ###
424
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
425
        self.DebugOutput()
1✔
426

427
    def ComputeFunctional(self,inflow_angle):
1✔
428
        self.fprint("Setting Up Taylor-Hood Problem",special="header")
1✔
429

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

436

437
        self.fprint("Viscosity:         {:1.2e}".format(float(self.viscosity)))
1✔
438
        self.fprint("Max Mixing Length: {:1.2e}".format(float(self.lmax)))
1✔
439

440

441
        ### Create the test/trial/functions ###
442
        self.v,self.q = TestFunctions(self.fs.W)
1✔
443

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

456
        ### Calculate nu_T
457
        self.nu_T=self.ComputeTurbulenceModel(self.u_k)
1✔
458

459
        ### Create the turbine force ###
460
        tf_term = self.ComputeTurbineForceTerm(self.u_k,self.v,inflow_angle)
1✔
461

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

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

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

481
        if self.use_25d_model:
1✔
482
            if self.dom.dim == 3:
1✔
483
                raise ValueError("The 2.5D model requires a 2D simulation.")
×
484

485
            self.fprint("Using 2.5D model")
1✔
486
            dudx = Dx(self.u_k[0], 0)
1✔
487
            dvdy = Dx(self.u_k[1], 1)
1✔
488

489
            if inflow_angle is None:
1✔
490
                term25 = dvdy*self.q*dx
×
491
            else:
492
                term25 = (abs(sin(inflow_angle))*dudx*self.q + abs(cos(inflow_angle))*dvdy*self.q)*dx
1✔
493

494
            self.F -= term25
1✔
495

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

498
# ================================================================
499

500
class IterativeSteady(GenericProblem):
1✔
501
    """
502
    The IterativeSteady sets up everything required for solving Navier-Stokes using
503
    the SIMPLE algorithm
504

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

515
        ### Create Functional ###
516
        self.ComputeFunctional(self.dom.inflow_angle)
×
517

518
    def ComputeFunctional(self,inflow_angle):
1✔
519
        # ================================================================
520

521
        # Define fluid properties
522
        # FIXME: These should probably be set in params.yaml input filt
523
        # nu = 1/10000
524
        nu = Constant(self.viscosity)
×
525

526
        # Trial functions for velocity and pressure
527
        u = TrialFunction(self.fs.V)
×
528
        p = TrialFunction(self.fs.Q)
×
529

530
        # Test functions for velocity and pressure
531
        v = TestFunction(self.fs.V)
×
532
        q = TestFunction(self.fs.Q)
×
533

534
        ### Define Velocity Functions ###
535
        self.u_k = Function(self.fs.V, name="u_k")
×
536
        self.u_hat = Function(self.fs.V)
×
537
        self.u_s = Function(self.fs.V)
×
538
        self.du = Function(self.fs.V)
×
539
        self.u_k_old = Function(self.fs.V)
×
540

541
        ### Define Pressure Functions ###
542
        self.p_k = Function(self.fs.Q, name="p_k")
×
543
        self.p_s = Function(self.fs.Q)
×
544
        self.dp = Function(self.fs.Q)
×
545
        self.p_k_old = Function(self.fs.Q)
×
546

NEW
547
        U_CN = 0.5*(u + self.u_k)
×
548

549
        # Adams-Bashforth velocity
550
        # U_AB = 1.5*u_k - 0.5*u_k_old # Time level k+1/2
551
        U_AB = 2.0*self.u_k - 1.0*self.u_k_old # Time level k+1
×
552

553
        # Compute eddy viscosity
554
        self.nu_T=self.ComputeTurbulenceModel(U_AB)
×
555

556
        # ================================================================
557

558
        # FIXME: This up_k function is only present to avoid errors
559
        # during assignments in GenericSolver.__init__
560

561
        # Create the combined function space
562
        self.up_k = Function(self.fs.W)
×
563

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

569
        tf_term = self.ComputeTurbineForceTerm(self.u_k,v,inflow_angle)
×
570
        # self.u_k.assign(self.bd.bc_velocity)
571

572
        # self.u_k2.vector()[:] = 0.0
573
        # self.u_k1.vector()[:] = 0.0
574

575
        # ================================================================
576

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

582
        # Add body force to functional
583
        F1 += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
584

585
        self.F1_lhs = lhs(F1)
×
586
        self.F1_rhs = rhs(F1)
×
587

588

589
        # Use u_hat to solve for the pressure field
590
        self.F2_lhs = inner(grad(p), grad(q))*dx
×
591
        self.F2_rhs = - div(self.u_hat)*q*dx
×
592

593
        self.dt_1 = Constant(1) # Becomes (1/dt, 0) for (unsteady, steady) state
×
594
        self.dt_2 = Constant(1) # Becomes (1/dt, 1) for (unsteady, steady) state
×
595
        self.dt_3 = Constant(1) # Becomes (  dt, 1) for (unsteady, steady) state
×
596

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

604
        # Add body force to functional
605
        F3 += inner(-self.mbody_force*self.bd.inflow_unit_vector,v)*dx
×
606

607
        self.F3_lhs = lhs(F3)
×
608
        self.F3_rhs = rhs(F3)
×
609

610

611
        # Define variational problem for step 2: pressure correction
612
        self.F4_lhs = inner(grad(p), grad(q))*dx
×
613
        self.F4_rhs = - self.dt_2*div(self.u_s)*q*dx
×
614

615

616
        # Define variational problem for step 3: velocity update
617
        self.F5_lhs = inner(u, v)*dx
×
618
        self.F5_rhs = - self.dt_3*inner(grad(self.dp), v)*dx
×
619

620
        # ================================================================
621

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

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

626
class UnsteadyProblem(GenericProblem):
1✔
627
    """
628
    The UnsteadyProblem sets up everything required for solving Navier-Stokes using
629
    a fractional-step method with an adaptive timestep size
630

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

641
        ### Create Functional ###
642
        self.ComputeFunctional(self.dom.inflow_angle)
1✔
643
        self.DebugOutput()
1✔
644

645
    def ComputeFunctional(self,inflow_angle):
1✔
646
        # ================================================================
647

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

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

661
        self.fprint("Viscosity: {:1.2e}".format(float(self.viscosity)))
1✔
662
        self.fprint("Density:   {:1.2e}".format(float(rho)))
1✔
663

664
        # Define trial and test functions for velocity
665
        u = TrialFunction(self.fs.V)
1✔
666
        v = TestFunction(self.fs.V)
1✔
667

668
        # Define trial and test functions for pressure
669
        p = TrialFunction(self.fs.Q)
1✔
670
        q = TestFunction(self.fs.Q)
1✔
671

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

680
        # Seed previous velocity fields with the chosen initial condition
681
        self.u_k.assign(self.bd.bc_velocity)
1✔
682
        self.u_k1.assign(self.bd.bc_velocity)
1✔
683
        self.u_k2.assign(self.bd.bc_velocity)
1✔
684

685
        # Calculate Reynolds stress
686
        self.uk_sum = Function(self.fs.V, name="uk_sum")
1✔
687
        self.uk_sum.assign(self.dt_c*self.u_k)
1✔
688
        self.vertKE = Function(self.fs.Q, name="vertKE")
1✔
689

690
        # Define functions for pressure solutions
691
        # >> _k = current (step k)
692
        # >> _k1 = previous (step k-1)
693
        self.p_k  = Function(self.fs.Q, name="p_k")
1✔
694
        self.p_k1 = Function(self.fs.Q, name="p_k1")
1✔
695

696
        # Seed previous pressure fields with the chosen initial condition
697
        self.p_k1.assign(self.bd.bc_pressure)
1✔
698

699
        # ================================================================
700

701
        # Crank-Nicolson velocity
702
        U_CN  = 0.5*(u + self.u_k1)
1✔
703

704
        # Adams-Bashforth projected velocity
705
        U_AB = 1.5*self.u_k1 - 0.5*self.u_k2
1✔
706

707
        # ================================================================
708
        self.nu_T = self.ComputeTurbulenceModel(U_AB)
1✔
709

710
        # ================================================================
711

712
        # FIXME: This up_k function is only present to avoid errors
713
        # during assignments in GenericSolver.__init__
714

715
        # Create the combined function space
716
        self.up_k = Function(self.fs.W, name="up_k")
1✔
717

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

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

725
        self.u_k.assign(self.bd.bc_velocity)
1✔
726

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

735
        # self.u_k2.vector()[:] = 0.0
736
        # self.u_k1.vector()[:] = 0.0
737

738
        # ================================================================
739

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

747
        # F1 = (1.0/self.dt_c)*inner(u - self.u_k1, v)*dx \
748
        #    + inner(dot(U_AB, nabla_grad(U_CN)), v)*dx \
749
        #    + (nu_c+self.nu_T)*inner(grad(U_CN), grad(v))*dx \
750
        #    + dot(nabla_grad(self.p_k1), v)*dx \
751
        #    - dot(self.tf, v)*dx
752

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

759
        self.a1 = lhs(F1)
1✔
760
        self.L1 = rhs(F1)
1✔
761

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

768
        # phi = p - self.p_k
769
        # F2 = inner(grad(q), grad(phi))*dx - (1.0/self.dt_c)*div(u_k)*q*dx
770
        # self.a2 = lhs(F2)
771
        # self.L2 = rhs(F2)
772

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

779
        # F3 = inner(u, v)*dx - inner(self.u_k, v)*dx + self.dt_c*inner(phi, v)*dx
780
        # self.a3 = lhs(F3)
781
        # self.L3 = rhs(F3)
782

783

784
        # ================================================================
785

786
        self.fprint("Unsteady Problem Setup",special="footer")
1✔
787

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

790
#     def UpdateActuatorLineControls(self, c_lift = None, c_drag = None):
791

792
#         if c_lift is not None:
793
#             cl = np.array(c_lift, dtype = float)
794
#         if c_drag is not None:
795
#             cd = np.array(c_drag, dtype = float)
796

797
#         for k in range(self.num_actuator_nodes):
798
#             self.mcl[k] = Constant(cl[k])
799
#             self.mcd[k] = Constant(cd[k])
800

801
# # ================================================================
802

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