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

NREL / WindSE / 10064946629

23 Jul 2024 07:11PM UTC coverage: 63.179% (-0.01%) from 63.193%
10064946629

push

github

web-flow
Patch fix (#118)

* added ability to integrate turbines locally

* changed name of new parallel CI file

* added local dx test problem

* fixed typo in power calc

* refactor wind inflow angle to be consistent with meteorlogical convention

* fixed the double count of turns for reconstructing bcs

* vectorized the angle clamp

6 of 15 new or added lines in 4 files covered. (40.0%)

1 existing line in 1 file now uncovered.

4710 of 7455 relevant lines covered (63.18%)

0.63 hits per line

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

44.58
/windse/SolverManager.py
1
"""
2
The SolverManager contains all the different ways to solve problems generated
3
in windse
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
    from sys import platform
1✔
20
    import time
1✔
21
    import numpy as np
1✔
22
    from scipy.interpolate import RegularGridInterpolator
1✔
23
    # from memory_profiler import memory_usage
24
    from scipy.special import gamma
1✔
25
    from mpmath import hyper
1✔
26
    import cProfile
1✔
27
    import pstats
1✔
28

29
    ### Import the cumulative parameters ###
30
    from windse import windse_parameters
1✔
31

32
    ### Import some helper functions
33
    from windse.helper_functions import meteor_to_math, math_to_meteor
1✔
34

35
    ### Check if we need dolfin_adjoint ###
36
    if windse_parameters.dolfin_adjoint:
1✔
37
        from dolfin_adjoint import *
1✔
38
        from windse.blocks import blockify, MarkerBlock, ControlUpdaterBlock
1✔
39
        from pyadjoint import AdjFloat
1✔
40

41
    ### Import objective functions ###
42
    import windse.objective_functions as obj_funcs
1✔
43

44
    ### This import improves the plotter functionality on Mac ###
45
    if platform == 'darwin':
1✔
46
        import matplotlib
1✔
47
        matplotlib.use('TKAgg')
1✔
48
    import matplotlib.pyplot as plt
1✔
49

50
    ### Check to see if the latest compiler representation is installed ###
51
    try:
1✔
52
        import tsfc
1✔
53
        default_representation = 'tsfc'
1✔
54
    except ImportError as e:
×
55
        default_representation = 'uflacs'
×
56

57
    ### Improved dolfin parameters ###
58
    parameters["std_out_all_processes"] = False;
1✔
59
    parameters['form_compiler']['cpp_optimize_flags'] = '-O3 -fno-math-errno -march=native'        
1✔
60
    parameters["form_compiler"]["optimize"]     = True
1✔
61
    parameters["form_compiler"]["cpp_optimize"] = True
1✔
62
    parameters['form_compiler']['representation'] = default_representation
1✔
63
    parameters['form_compiler']['quadrature_degree'] = windse_parameters["function_space"]["quadrature_degree"]
1✔
64
    
65
class GenericSolver(object):
1✔
66
    """
67
    A GenericSolver contains on the basic functions required by all solver objects.
68
    """
69
    def __init__(self,problem):
1✔
70
        self.params = windse_parameters
1✔
71
        self.problem  = problem
1✔
72
        self.nu_T = self.problem.nu_T
1✔
73
        self.first_save = True
1✔
74
        self.fprint = self.params.fprint
1✔
75
        self.tag_output = self.params.tag_output
1✔
76
        self.debug_mode = self.params.debug_mode
1✔
77
        self.simTime = 0.0
1✔
78
        self.simTime_prev = None
1✔
79
        self.iter_val = 0.0
1✔
80
        self.pow_saved = False
1✔
81

82
        ### Update attributes based on params file ###
83
        for key, value in self.params["solver"].items():
1✔
84
            setattr(self,key,value)
1✔
85

86
        ### Update attributes based on params file ###
87
        for key, value in self.params["optimization"].items():
1✔
88
            setattr(self,key,value)
1✔
89

90
        self.extra_kwarg = {}            
1✔
91
        if self.params.dolfin_adjoint:
1✔
92
            self.extra_kwarg["annotate"] = False
1✔
93

94
        J_temp = 0.0
1✔
95
        if self.params["general"]["dolfin_adjoint"]:
1✔
96
            J_temp = AdjFloat(0.0)
1✔
97

98
        self.optimizing = False
1✔
99
        if self.params.performing_opt_calc or self.save_objective:
1✔
100
            self.optimizing = True
1✔
101
            self.J = J_temp
1✔
102
            self.adj_file = XDMFFile(self.params.folder+"timeSeries/local_adjoint.xdmf")
1✔
103
            self.adj_time_iter = 1
1✔
104
            self.adj_time_list = [0.0]
1✔
105
            self.J_saved = False
1✔
106

107
            if isinstance(self.opt_turb_id,int):
1✔
108
                self.opt_turb_id = [self.opt_turb_id]
×
109
            elif self.opt_turb_id == "all":
1✔
110
                self.opt_turb_id = range(self.problem.farm.numturbs) 
1✔
111
            elif isinstance(self.opt_turb_id, str):
×
112
                self.opt_turb_id = [int(self.opt_turb_id)]
×
113

114
        # blockify custom functions so dolfin adjoint can track them
115
        if self.params.performing_opt_calc:
1✔
116
        #     self.marker = blockify(self.marker,MarkerBlock)
117
            self.control_updater = blockify(self.control_updater,ControlUpdaterBlock)
1✔
118

119
        #Check if we need to save the power output
120
        if self.save_power:
1✔
121
            self.J = J_temp
1✔
122
            self.J_saved = False
1✔
123

124
    @no_annotations
1✔
125
    def DebugOutput(self,t=None,i=None):
1✔
126
        if self.debug_mode:
1✔
127
            if self.problem.dom.dim == 3:
1✔
128
                ux, uy, uz = self.problem.u_k.split(True)
1✔
129
            else:
130
                ux, uy = self.problem.u_k.split(True)
1✔
131

132
            if t is None:
1✔
133
                suffix = ""
1✔
134
            else:
135
                suffix = "_"+repr(i)
1✔
136
                self.tag_output("time"+suffix,t)
1✔
137

138
            self.tag_output("min_x_vel"+suffix,ux.vector().min()) # probably not the fastest way to get the average velocity
1✔
139
            self.tag_output("max_x_vel"+suffix,ux.vector().max()) # probably not the fastest way to get the average velocity
1✔
140
            self.tag_output("avg_x_vel"+suffix,assemble(ux*dx)/self.problem.dom.volume) # probably not the fastest way to get the average velocity
1✔
141
            self.tag_output("min_y_vel"+suffix,uy.vector().min()) # probably not the fastest way to get the average velocity
1✔
142
            self.tag_output("max_y_vel"+suffix,uy.vector().max()) # probably not the fastest way to get the average velocity
1✔
143
            self.tag_output("avg_y_vel"+suffix,assemble(uy*dx)/self.problem.dom.volume) # probably not the fastest way to get the average velocity
1✔
144
            if self.problem.dom.dim == 3:
1✔
145
                self.tag_output("min_z_vel"+suffix,uz.vector().min()) # probably not the fastest way to get the average velocity
1✔
146
                self.tag_output("max_z_vel"+suffix,uz.vector().max()) # probably not the fastest way to get the average velocity
1✔
147
                self.tag_output("avg_z_vel"+suffix,assemble(uy*dx)/self.problem.dom.volume) # probably not the fastest way to get the average velocity
1✔
148

149

150

151
    @no_annotations
1✔
152
    def Save(self,val=0):
1✔
153
        """
154
        This function saves the mesh and boundary markers to output/.../solutions/
155
        """
156
        u = self.problem.u_k
1✔
157
        p = self.problem.p_k
1✔
158

159
        u.vector()[:]=u.vector()[:]/self.problem.dom.xscale
1✔
160
        self.problem.dom.mesh.coordinates()[:]=self.problem.dom.mesh.coordinates()[:]/self.problem.dom.xscale
1✔
161

162
        if self.first_save:
1✔
163
            self.u_file = self.params.Save(u,"velocity",subfolder="solutions/",val=val)
1✔
164
            self.p_file = self.params.Save(p,"pressure",subfolder="solutions/",val=val)
1✔
165
            self.nuT_file = self.params.Save(self.nu_T,"eddy_viscosity",subfolder="solutions/",val=val)
1✔
166
            if self.problem.dom.dim == 3:
1✔
167
                self.ReyStress_file = self.params.Save(self.ReyStress,"Reynolds_stresses",subfolder="solutions/",val=val)
1✔
168
                self.vertKE_file = self.params.Save(self.vertKE,"Vertical KE",subfolder="solutions/",val=val)
1✔
169
            self.first_save = False
1✔
170
        else:
171
            self.params.Save(u,"velocity",subfolder="solutions/",val=val,file=self.u_file)
×
172
            self.params.Save(p,"pressure",subfolder="solutions/",val=val,file=self.p_file)
×
173
            self.params.Save(self.nu_T,"eddy_viscosity",subfolder="solutions/",val=val,file=self.nuT_file)
×
174
            if self.problem.dom.dim == 3:
×
175
                self.params.Save(self.ReyStress,"Reynolds_stresses",subfolder="solutions/",val=val,file=self.ReyStress_file)
×
176
                self.params.Save(self.vertKE,"Vertical KE",subfolder="solutions/",val=val,file=self.vertKE_file)
×
177
        u.vector()[:]=u.vector()[:]*self.problem.dom.xscale
1✔
178
        self.problem.dom.mesh.coordinates()[:]=self.problem.dom.mesh.coordinates()[:]*self.problem.dom.xscale
1✔
179

180
    def ChangeWindSpeed(self,inflow_speed):
1✔
181
        """
182
        This function recomputes all necessary components for a new wind direction
183

184
        Args: 
185
            theta (float): The new wind angle in radians
186
        """
187
        self.problem.ChangeWindSpeed(inflow_speed)
×
188

189
    def ChangeWindAngle(self,inflow_angle):
1✔
190
        """
191
        This function recomputes all necessary components for a new wind direction
192

193
        Args: 
194
            theta (float): The new wind angle in radians
195
        """
196
        self.problem.ChangeWindAngle(inflow_angle)
×
197

198
    # def SavePower(self,inflow_angle=0.0):
199

200
    #     J_list=np.zeros(self.problem.farm.numturbs+1)
201

202
    #     if self.problem.farm.actuator_disks_list is not None:
203
    #         for i in range(self.problem.farm.numturbs):
204
    #             yaw = self.problem.farm.myaw[i]+inflow_angle
205
    #             tf1 = self.problem.farm.actuator_disks_list[i] * cos(yaw)**2
206
    #             tf2 = self.problem.farm.actuator_disks_list[i] * sin(yaw)**2
207
    #             tf3 = self.problem.farm.actuator_disks_list[i] * 2.0 * cos(yaw) * sin(yaw)
208
    #             tf = tf1*self.u_k[0]**2+tf2*self.u_k[1]**2+tf3*self.u_k[0]*self.u_k[1]
209
    #             J_list[i] = assemble(dot(tf,self.u_k)*dx,**self.extra_kwarg)
210
    #     J_list[-1]=sum(J_list)
211

212
    #     folder_string = self.params.folder+"/data/"
213
    #     if not os.path.exists(folder_string): os.makedirs(folder_string)
214

215
    #     if self.J_saved:
216
    #         f = open(folder_string+"power_data.txt",'ab')
217
    #     else:
218
    #         f = open(folder_string+"power_data.txt",'wb')
219
    #         self.J_saved = True
220

221
    #     np.savetxt(f,[J_list])
222
    #     f.close()
223

224
    @no_annotations
1✔
225
    def EvaulatePowerFunctional(self):
1✔
226
        kwargs = {
1✔
227
            "iter_val": self.iter_val, 
228
            "simTime": self.simTime
229
        }
230
        out = self.problem.farm.save_power(self.problem.u_k,self.problem.dom.inflow_angle, **kwargs)
1✔
231
        return out
1✔
232

233

234
    def EvaluateObjective(self,output_name="objective_data",opt_iter=-1):
1✔
235
        self.fprint("Evaluating Objective Data",special="header")
1✔
236
        start = time.time()
1✔
237

238
        first_call = True
1✔
239
        if self.J_saved:
1✔
240
            first_call = False
1✔
241

242
        annotate = self.params.dolfin_adjoint 
1✔
243

244
        ### Iterate over objectives ###
245
        obj_list = [opt_iter, self.iter_val, self.simTime]
1✔
246
        for key, obj_kwargs in self.objective_type.items():
1✔
247
            if isinstance(key, int):
1✔
248
                objective_name = obj_kwargs["type"]
×
249
            else:
250
                objective_name = key
1✔
251
            objective_func = obj_funcs.objective_functions[objective_name]
1✔
252
            args = (self, (self.problem.dom.inflow_angle))
1✔
253
            kwargs = {"first_call": first_call, "annotate": annotate}
1✔
254
            kwargs.update(obj_kwargs)
1✔
255
            out = obj_funcs._annotated_objective(objective_func, *args, **kwargs)
1✔
256
            obj_list.append(out)
1✔
257
        J = obj_list[3] #grab first objective 
1✔
258

259
        # ### Flip the sign because the objective is minimized but these values are maximized
260
        # for i in range(1,len(obj_list)):
261
        #     obj_list[i] = -obj_list[i]
262

263
        ### Save to csv ###
264
        if self.J_saved:
1✔
265
            self.params.save_csv(output_name,data=[obj_list],subfolder=self.params.folder+"data/",mode='a')
1✔
266
        else:
267
            ### Generate the header ###
268
            header = "Opt_iter, Iter_Val, Time, "
1✔
269
            for key, val in self.objective_type.items():
1✔
270
                if isinstance(key, int):
1✔
271
                    name = val["type"]
×
272
                else:
273
                    name = key
1✔
274
                header += name + ", "
1✔
275
            header = header[:-2]
1✔
276

277
            self.params.save_csv(output_name,header=header,data=[obj_list],subfolder=self.params.folder+"data/",mode='w')
1✔
278
            self.J_saved = True
1✔
279

280
        stop = time.time()
1✔
281
        self.fprint("Complete: {:1.2f} s".format(stop-start),special="footer")
1✔
282
        return J
1✔
283

284
    def marker(self, u, simTime, adj_tape_file):
1✔
285
        return u
×
286

287
    def control_updater(self, J, problem, time=None):
1✔
288
        return J
1✔
289

290

291

292

293

294
class SteadySolver(GenericSolver):
1✔
295
    """
296
    This solver is for solving the steady state problem
297

298
    Args: 
299
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
300
    """
301
    def __init__(self,problem):
1✔
302
        super(SteadySolver, self).__init__(problem)
1✔
303
        self.u_k,self.p_k = split(self.problem.up_k)
1✔
304

305
    def Solve(self):
1✔
306
        """
307
        This solves the problem setup by the problem object.
308
        """
309

310
        ### Save Files before solve ###
311
        self.fprint("Saving Input Data",special="header")
1✔
312
        if "mesh" in self.params.output:
1✔
313
            self.problem.dom.Save(val=self.iter_val)
1✔
314
        if "initial_guess" in self.params.output:
1✔
315
            self.problem.bd.SaveInitialGuess(val=self.iter_val)
1✔
316
        if "height" in self.params.output and self.problem.dom.dim == 3:
1✔
317
            self.problem.bd.SaveHeight(val=self.iter_val)
1✔
318
        if "turbine_force" in self.params.output:
1✔
319
            self.problem.farm.save_functions(val=self.iter_val)
1✔
320
        self.fprint("Finished",special="footer")
1✔
321

322
        # exit()
323

324
        ####################################################################
325
        ### This is the better way to define a nonlinear problem but it 
326
        ### doesn't play nice with dolfin_adjoint
327
        ### Define Jacobian ###
328
        # dU = TrialFunction(self.problem.fs.W)
329
        # J  = derivative(self.problem.F,  self.problem.up_k, dU)
330

331
        # ### Setup nonlinear solver ###
332
        # nonlinear_problem = NonlinearVariationalProblem(self.problem.F, self.problem.up_k, self.problem.bd.bcs, J)
333
        # nonlinear_solver  = NonlinearVariationalSolver(nonlinear_problem)
334

335
        # ### Set some parameters ###
336
        # solver_parameters = nonlinear_solver.parameters['newton_solver']['linear_solver'] = 'gmres'
337

338
        # # nonlinear_solver.ksp().setGMRESRestart(40)
339

340
        # def print_nested_dict(d, indent):
341
        #     for key, value in d.items():
342
        #         if hasattr(value, 'items'):
343
        #             for i in range(indent):
344
        #                 print('\t', end = '')
345
        #             print(key, ":")
346
        #             indent += 1
347
        #             print_nested_dict(value, indent)
348
        #             indent -= 1
349
        #         else:
350
        #             for i in range(indent):
351
        #                 print('\t', end = '')
352
        #             print(key, ":", value)
353

354
        # nonlinear_solver.parameters
355
        # print_nested_dict(nonlinear_solver.parameters, 0)
356
        # exit()
357

358

359

360
        # print(type(solver_parameters))
361

362
        # info(solver_parameters)
363
        # solver_parameters["nonlinear_solver"] = "snes"
364
        # solver_parameters["snes_solver"]["linear_solver"] = "mumps"
365
        # solver_parameters["snes_solver"]["maximum_iterations"] = 50
366
        # solver_parameters["snes_solver"]["error_on_nonconvergence"] = False
367
        # solver_parameters["snes_solver"]["line_search"] = "bt" # Available: basic, bt, cp, l2, nleqerr
368

369
        ### Solve the problem ###
370
        # self.fprint("Solving",special="header")
371
        # start = time.time()
372
        # iters, converged = nonlinear_solver.solve()
373
        # stop = time.time()
374
        # self.fprint("Total Nonlinear Iterations: {:d}".format(iters))
375
        # self.fprint("Converged Successfully: {0}".format(converged))
376
        ####################################################################
377

378
        self.fprint("Solving with {0}".format(self.nonlinear_solver))
1✔
379
        if self.nonlinear_solver == "newton":
1✔
380
            self.fprint("Relaxation parameter = {: 1.2f}".format(self.newton_relaxation))
1✔
381

382
            krylov_options = {"absolute_tolerance": 9e-3,
1✔
383
                              "relative_tolerance": 9e-1,
384
                              "maximum_iterations": 5000}
385

386
            newton_options = {"relaxation_parameter": self.newton_relaxation,
1✔
387
                              "maximum_iterations": 150,
388
                              "linear_solver": "mumps",
389
                              "preconditioner": "default",
390
                              "absolute_tolerance": 1e-6,
391
                              "relative_tolerance": 1e-5,
392
                              "error_on_nonconvergence": False,
393
                              "krylov_solver": krylov_options}
394
        
395
            solver_parameters = {"nonlinear_solver": "newton",
1✔
396
                                 "newton_solver": newton_options}
397

398
        elif self.nonlinear_solver == "snes":
1✔
399
            # ### Add some helper functions to solver options ###
400

401
            krylov_options = {"absolute_tolerance":  1e-12,
1✔
402
                              "relative_tolerance":  1e-6,
403
                              "maximum_iterations":  5000,
404
                              "monitor_convergence": True}
405

406
            solver_parameters = {"nonlinear_solver": "snes",
1✔
407
                                 "snes_solver": {
408
                                 "absolute_tolerance": 1e-6,
409
                                 "relative_tolerance": 1e-5,
410
                                 "linear_solver": "mumps",
411
                                 "preconditioner": "default",
412
                                 "maximum_iterations": 150,
413
                                 "error_on_nonconvergence": False,
414
                                 "line_search": "bt", #[basic,bt,cp,l2,nleqerr]
415
                                 "krylov_solver": krylov_options
416
                                 }}  
417

418
        else:
419
            raise ValueError("Unknown nonlinear solver type: {0}".format(self.nonlinear_solver))
×
420

421
        ### Start the Solve Process ###
422
        self.fprint("Solving",special="header")
1✔
423
        start = time.time()
1✔
424

425
        # ### Solve the Baseline Problem ###
426
        # solve(self.problem.F_sans_tf == 0, self.problem.up_k, self.problem.bd.bcs, solver_parameters=solver_parameters, **self.extra_kwarg)
427

428
        # ### Store the Baseline and Assign for the real solve ###
429
        # self.up_baseline = self.problem.up_k.copy(deepcopy=True)
430
        # self.problem.up_k.assign(self.up_baseline)
431

432
        ### Solve the real problem ###
433
        # mem0=memory_usage()[0]
434
        # mem_out, _ = memory_usage((solve,(self.problem.F == 0, self.problem.up_k, self.problem.bd.bcs),{"solver_parameters": solver_parameters}),max_usage=True,retval=True,max_iterations=1)
435
        solve(self.problem.F == 0, self.problem.up_k, self.problem.bd.bcs, solver_parameters=solver_parameters)
1✔
436
        stop = time.time()
1✔
437

438
        self.fprint("Solve Complete: {:1.2f} s".format(stop-start),special="footer")
1✔
439
        # self.fprint("Memory Used:  {:1.2f} MB".format(mem_out-mem0))
440
        # self.u_k,self.p_k = self.problem.up_k.split(True)
441
        self.problem.u_k,self.problem.p_k = self.problem.up_k.split()
1✔
442

443
        # try:
444
        #     print(f"u(0, 0,150):   {self.problem.u_k([0.0, 0.0,150.0])}")
445
        # except:
446
        #     pass
447
        # try:
448
        #     print(f"u(0, 0,210):   {self.problem.u_k([0.0, 0.0,210.0])}")
449
        # except:
450
        #     pass
451
        # try:
452
        #     print(f"u(0,60,150):   {self.problem.u_k([0.0,60.0,150.0])}")
453
        # except:
454
        #     pass
455
        # print(f"max(u):        {self.problem.u_k.vector().max()}")
456
        # print(f"min(u):        {self.problem.u_k.vector().min()}")
457
        # print(f"integral(u_x): {assemble(self.problem.u_k[0]*dx)}")
458

459

460
        ### Hack into doflin adjoint to update the local controls at the start of the adjoint solve ###
461
        self.nu_T = project(self.problem.nu_T,self.problem.fs.Q,solver_type='gmres',preconditioner_type="hypre_amg",**self.extra_kwarg)
1✔
462
        if self.problem.dom.dim == 3:
1✔
463
            self.fprint("")
1✔
464
            self.fprint("Projecting Reynolds Stress")
1✔
465
            self.ReyStress = project(self.problem.ReyStress,self.problem.fs.T,solver_type='gmres',preconditioner_type="hypre_amg",**self.extra_kwarg)
1✔
466
            self.vertKE = project(self.problem.vertKE,self.problem.fs.Q,solver_type='gmres',preconditioner_type="hypre_amg",**self.extra_kwarg)
1✔
467

468
        # self.nu_T = None
469

470
        ### Save solutions ###
471
        if "solution" in self.params.output:
1✔
472
            self.fprint("Saving Solution",special="header")
1✔
473
            self.Save(val=self.iter_val)
1✔
474
            self.fprint("Finished",special="footer")
1✔
475

476
        ### calculate the power for each turbine ###
477
        ###################################
478
        ### Fix how angle is transfered ###
479
        ###################################
480
        # if self.save_power:
481
        #     self.SavePower(((self.iter_theta-self.problem.dom.inflow_angle)))
482

483

484
        ### Evaluate the objectives ###
485
        if self.optimizing or self.save_objective:
1✔
486
            self.J += self.EvaluateObjective()
1✔
487
            # self.J += self.objective_func(self,(self.iter_theta-self.problem.dom.inflow_angle)) 
488
            self.J = self.control_updater(self.J, self.problem)
1✔
489

490
        if self.save_power:
1✔
491
            self.EvaulatePowerFunctional()
1✔
492

493
            # print(self.outflow_markers)
494
            # self.J += -dot(self.problem.farm.rotor_disks,self.u_k)*dx
495

496
        # self.fprint("Speed Percent of Inflow Speed")
497
        # ps = []
498
        # for i in range(6):
499
        #     HH = self.problem.farm.HH[0]
500
        #     RD = self.problem.farm.RD[0]
501
        #     x_val = (i+1)*RD
502
        #     vel = self.problem.up_k([x_val,0,HH])
503
        #     vel = vel[0:3]
504
        #     nom = np.linalg.norm(vel)
505
        #     perc = nom/self.problem.bd.HH_vel
506
        #     ps.append(perc)
507
        #     self.fprint("Speed Percent at ("+repr(int(x_val))+", 0, "+repr(HH)+"): "+repr(perc))
508
        # print(ps)
509

510
        self.DebugOutput()
1✔
511

512
# 
513
# ================================================================
514

515
class IterativeSteadySolver(GenericSolver):
1✔
516
    """
517
    This solver is for solving the iterative steady state problem
518

519
    Args: 
520
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
521
    """
522
    def __init__(self,problem):
1✔
523
        super(IterativeSteadySolver, self).__init__(problem)
×
524
        self.u_k,self.p_k = split(self.problem.up_k)
×
525

526
    def Solve(self):
1✔
527
        """
528
        This solves the problem setup by the problem object.
529
        """
530

531
        def set_solver_mode(solver_type):
×
532
            if solver_type == 'steady':
×
533
                self.problem.dt_1.assign(0)
×
534
                self.problem.dt_2.assign(1)
×
535
                self.problem.dt_3.assign(1)
×
536

537
                sor_vel = 0.1
×
538
                sor_pr = 0.2
×
539

540
            elif solver_type == 'unsteady':
×
541
                delta_t = 100.0
×
542

543
                self.problem.dt_1.assign(1.0/delta_t)
×
544
                self.problem.dt_2.assign(1.0/delta_t)
×
545
                self.problem.dt_3.assign(delta_t)
×
546

547
                sor_vel = 1.0
×
548
                sor_pr = 1.0
×
549

550
            else:
551
                raise ValueError('Solver type should be "steady" or "unsteady".')
×
552

553
            return sor_vel, sor_pr
×
554

555
        def get_relaxation_param(a_min, a_max, a_c, a_cw, x):
×
556
            
557
            # a_min = minimum sor value
558
            # a_max = maximum sor value
559
            # a_c = crossover location
560
            # a_cw = crossover width
561

562
            alpha_exp = (1/a_cw)*(a_c - x)
×
563
            alpha = 1.0/(1.0+np.exp(alpha_exp))
×
564
            alpha = alpha*(a_max-a_min) + a_min
×
565
            alpha = float(alpha)
×
566
            
567
            return alpha
×
568

569
        ### Save Files before solve ###
570
        self.fprint("Saving Input Data",special="header")
×
571
        if "mesh" in self.params.output:
×
572
            self.problem.dom.Save(val=self.iter_val)
×
573
        if "initial_guess" in self.params.output:
×
574
            self.problem.bd.SaveInitialGuess(val=self.iter_val)
×
575
        if "height" in self.params.output and self.problem.dom.dim == 3:
×
576
            self.problem.bd.SaveHeight(val=self.iter_val)
×
577
        if "turbine_force" in self.params.output:
×
578
            self.problem.farm.SaveActuatorDisks(val=self.iter_val)
×
579
        self.fprint("Finished",special="footer")
×
580

581
        self.SaveTimeSeries(0)
×
582

583
        # Assemble left-hand side matrices
584
        A1 = assemble(self.problem.F1_lhs)
×
585
        A2 = assemble(self.problem.F2_lhs)
×
586
        A3 = assemble(self.problem.F3_lhs)
×
587
        A4 = assemble(self.problem.F4_lhs)
×
588
        A5 = assemble(self.problem.F5_lhs)
×
589

590
        # Apply boundary conditions to matrices
591
        [bc.apply(A1) for bc in self.problem.bd.bcu]
×
592
        [bc.apply(A2) for bc in self.problem.bd.bcp]
×
593
        [bc.apply(A3) for bc in self.problem.bd.bcu]
×
594
        [bc.apply(A4) for bc in self.problem.bd.bcp]
×
595

596
        # Assemble right-hand side vectors
597
        b1 = assemble(self.problem.F1_rhs)
×
598
        b2 = assemble(self.problem.F2_rhs)
×
599
        b3 = assemble(self.problem.F3_rhs)
×
600
        b4 = assemble(self.problem.F4_rhs)
×
601
        b5 = assemble(self.problem.F5_rhs)
×
602

603
        # Apply bounday conditions to vectors
604
        [bc.apply(b1) for bc in self.problem.bd.bcu]
×
605
        [bc.apply(b2) for bc in self.problem.bd.bcp]
×
606
        [bc.apply(b3) for bc in self.problem.bd.bcu]
×
607
        [bc.apply(b4) for bc in self.problem.bd.bcp]
×
608

609
        # Create solvers using the set_operator method
610
        # which ensures preconditioners are reused when possible
611
        # solver_1 = PETScKrylovSolver('gmres', 'jacobi')
612

613
        vel_sol_options = ['gmres', 'none']
×
614
        pr_sol_options = ['bicgstab', 'none']
×
615

616
        solver_1 = PETScKrylovSolver(vel_sol_options[0], vel_sol_options[1])
×
617
        solver_1.set_operator(A1)
×
618

619
        solver_2 = PETScKrylovSolver(pr_sol_options[0], pr_sol_options[1])
×
620
        solver_2.set_operator(A2)
×
621

622
        solver_3 = PETScKrylovSolver(vel_sol_options[0], vel_sol_options[1])
×
623
        solver_3.set_operator(A3)
×
624

625
        solver_4 = PETScKrylovSolver(pr_sol_options[0], pr_sol_options[1])
×
626
        solver_4.set_operator(A4)
×
627

628
        solver_5 = PETScKrylovSolver('cg', 'jacobi')
×
629
        solver_5.set_operator(A5)
×
630

631

632

633
        outer_tic = time.time()
×
634

635

636
        solver_type = 'steady'
×
637
        sor_vel, sor_pr = set_solver_mode(solver_type)
×
638

639
        seed_velocity = False
×
640
        seed_pressure = False
×
641

642

643
        if seed_velocity:
×
644
            self.problem.u_k.assign(self.problem.bd.bc_velocity)
×
645
            self.problem.u_k_old.assign(self.problem.bd.bc_velocity)
×
646

647
        if seed_pressure:
×
648
            if self.params.rank == 0:
×
649
                print('Calculating initial pressure from velocity guess.')
×
650

651
            A1 = assemble(self.problem.F1_lhs, tensor=A1)
×
652
            [bc.apply(A1) for bc in self.problem.bd.bcu]
×
653
            solver_1.set_operator(A1)
×
654

655
            # Step 1: Tentative velocity step
656
            b1 = assemble(self.problem.F1_rhs, tensor=b1)
×
657
            [bc.apply(b1) for bc in self.problem.bd.bcu]
×
658
            solver_1.solve(self.problem.u_hat.vector(), b1)
×
659

660
            # Step 2: Pressure correction step
661
            b2 = assemble(self.problem.F2_rhs, tensor=b2)
×
662
            [bc.apply(b2) for bc in self.problem.bd.bcp]
×
663

664
            solver_2.solve(self.problem.p_k.vector(), b2)
×
665

666

667

668
        max_iter = 10
×
669

670
        for iter_num in range(max_iter):
×
671
            tic = time.time()
×
672

673
            # sor_vel = get_relaxation_param(1e-3, 0.5, 100, 10, iter_num)
674
            sor_vel = get_relaxation_param(1e-1, 0.5, 20, 5, iter_num)
×
675
            # sor_vel = 0.1
676
            sor_pr = 1.6*sor_vel
×
677

678
            use_simpler = False
×
679

680
            # if iter_num > 0.9*max_iter and solver_type == 'unsteady':
681
            #     solver_type = 'steady'
682
                # dt_1, dt_2, dt_3, sor_vel, sor_pr = set_solver_mode(solver_type)
683

684
            if use_simpler:
×
685
                A1 = assemble(self.problem.F1_lhs, tensor=A1)
×
686
                [bc.apply(A1) for bc in self.problem.bd.bcu]
×
687
                solver_1.set_operator(A1)
×
688

689
                # Step 1: Tentative velocity step
690
                b1 = assemble(self.problem.F1_rhs, tensor=b1)
×
691
                [bc.apply(b1) for bc in self.problem.bd.bcu]
×
692
                solver_1.solve(self.problem.u_hat.vector(), b1)
×
693

694
                # u_hat.assign((1.0-sor_vel)*u_k + sor_vel*u_hat)
695

696
                # Step 2: Pressure correction step
697
                b2 = assemble(self.problem.F2_rhs, tensor=b2)
×
698
                [bc.apply(b2) for bc in self.problem.bd.bcp]
×
699

700
                solver_2.solve(self.problem.p_k.vector(), b2)
×
701

702

703
            # Re-solve for velocity
704
            A3 = assemble(self.problem.F3_lhs, tensor=A3)
×
705
            [bc.apply(A3) for bc in self.problem.bd.bcu]
×
706
            solver_3.set_operator(A3)
×
707

708
            # Step 1: Tentative velocity step
709
            b3 = assemble(self.problem.F3_rhs, tensor=b3)
×
710
            [bc.apply(b3) for bc in self.problem.bd.bcu]
×
711
            solver_3.solve(self.problem.u_s.vector(), b3)
×
712

713
            self.problem.u_s.assign((1.0-sor_vel)*self.problem.u_k + sor_vel*self.problem.u_s)
×
714

715
            # Step 2: Pressure correction step
716
            # Don't need A4 rebuild
717
            b4 = assemble(self.problem.F4_rhs, tensor=b4)
×
718
            [bc.apply(b4) for bc in self.problem.bd.bcp]
×
719
            solver_4.solve(self.problem.dp.vector(), b4)
×
720

721

722
            # Step 3: Velocity correction step
723
            b5 = assemble(self.problem.F5_rhs, tensor=b5)
×
724
            # [bc.apply(b3) for bc in bcu]
725
            solver_5.solve(self.problem.du.vector(), b5)
×
726

727

728
            norm_du = norm(self.problem.du)
×
729
            norm_dp = norm(self.problem.dp)
×
730
            # u_conv.append(norm_du)
731
            # p_conv.append(norm_dp)
732

733
            self.problem.u_k_old.assign(self.problem.u_k)
×
734
            self.problem.p_k_old.assign(self.problem.p_k)
×
735

736
            self.problem.u_k.assign(self.problem.u_s + self.problem.du)
×
737

738
            # p_k.assign(p_k + dp)
739
            # p_k.assign(p_k + sor_pr*dp)
740

741
            if not use_simpler:
×
742
                # p_k.assign(p_k + dp)
743
                self.problem.p_k.assign(self.problem.p_k + sor_pr*self.problem.dp)
×
744

745
            # u_k1.assign(u_k)
746
            # p_k1.assign(p_k)
747

748
            if (iter_num+1) % 1 == 0:
×
749
                self.SaveTimeSeries(iter_num+1)
×
750

751
            toc = time.time()
×
752

753
            if self.params.rank == 0:
×
754
                print('Step %3d of %3d, sor = (%.3f, %.3f): | du | = %.6e, | dp | = %.6e, CPU Time = %.2f s' % (iter_num+1, max_iter, sor_vel, sor_pr, norm_du, norm_dp, toc-tic))
×
755

756
        outer_toc = time.time()
×
757
        if self.params.rank == 0:
×
758
            print('TOTAL CPU TIME = %f seconds' % (outer_toc - outer_tic))
×
759

760
        ### Evaluate the objectives ###
761
        if self.optimizing or self.save_objective:
×
762
            self.J += self.EvaluateObjective()
×
763
            # self.J += self.objective_func(self,(self.iter_theta-self.problem.dom.inflow_angle)) 
764
            self.J = self.control_updater(self.J, self.problem)
×
765

766
        if self.save_power and "power":
×
767
            self.EvaulatePowerFunctional()
×
768

769
    @no_annotations
1✔
770
    def SaveTimeSeries(self, simTime):
1✔
771
        # if hasattr(self.problem,"tf_save"):
772
        #     self.problem.tf_save.vector()[:] = 0
773
        #     for fun in self.problem.tf_list:
774
        #         self.problem.tf_save.vector()[:] = self.problem.tf_save.vector()[:] + fun.vector()[:]
775
        # else:
776
        #     self.problem.tf_save = Function(self.problem.fs.V)
777
        #     for fun in self.problem.tf_list:
778
        #         self.problem.tf_save.vector()[:] = self.problem.tf_save.vector()[:] + fun.vector()[:]
779

780

781
        if self.first_save:
×
782
            self.velocity_file = self.params.Save(self.problem.u_k,"velocity",subfolder="timeSeries/",val=simTime)
×
783
            self.pressure_file   = self.params.Save(self.problem.p_k,"pressure",subfolder="timeSeries/",val=simTime)
×
784
            # self.turb_force_file   = self.params.Save(self.problem.tf_save,"turbine_force",subfolder="timeSeries/",val=simTime)
785
            # if self.optimizing:
786
                # self.adj_tape_file = XDMFFile(self.params.folder+"timeSeries/global_adjoint.xdmf")
787
                # self.problem.u_k1.assign(Marker(self.problem.u_k,simTime,self.adj_tape_file))
788
            self.first_save = False
×
789
        else:
790
            self.params.Save(self.problem.u_k,"velocity",subfolder="timeSeries/",val=simTime,file=self.velocity_file)
×
791
            self.params.Save(self.problem.p_k,"pressure",subfolder="timeSeries/",val=simTime,file=self.pressure_file)
×
792
            # self.params.Save(self.problem.tf_save,"turbine_force",subfolder="timeSeries/",val=simTime,file=self.turb_force_file)
793

794
# ================================================================
795

796
class UnsteadySolver(GenericSolver):
1✔
797
    """
798
    This solver is for solving an unsteady problem.  As such, it contains
799
    additional time-stepping features and functions not present in other solvers.
800
    This solver can only be used if an unsteady problem has been specified in
801
    the input file.
802

803
    Args: 
804
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
805
    """
806
    def __init__(self,problem):
1✔
807
        super(UnsteadySolver, self).__init__(problem)
1✔
808

809
    # ================================================================
810

811
    def Solve(self):
1✔
812
        # Start the unsteady solver ONLY if an unsteady problem has been created
813
        if self.problem.params["problem"]["type"] == 'unsteady':
1✔
814
            self.fprint("Solving with UnsteadySolver", special="header")
1✔
815
        else:
816
            raise ValueError("UnsteadySolver can only be run with ProblemType = unsteady, not %s" \
×
817
                % (self.problem.params["problem"]["type"]))
818

819
        if type(self.final_time) == str and self.final_time.lower() == 'none':
1✔
820
            self.pseudo_steady = True
×
821
            self.fprint('Found option "None" for final_time, ')
×
822
            self.fprint('Running until unsteady solver is converged.')
×
823
            self.final_time = 1000000.0
×
824
            #self.final_time = 10.0
825

826
        # ================================================================
827
        
828
        # Start a counter for the total simulation time
829
        self.fprint("dt: %.4f" % (self.problem.dt))
1✔
830
        self.fprint("Final Time: %.1f" % (self.final_time))
1✔
831

832
        # Calculate the time to start recording if optimizing
833
        if self.optimizing:
1✔
834
            if self.record_time == "computed":
1✔
835
                self.record_time = 1.0*(self.wake_RD*self.problem.farm.RD[0]/(self.problem.bd.HH_vel*0.75))
×
836
                if self.final_time < self.record_time + 60.0/self.problem.rpm:
×
837
                    self.fprint("Warning: Final time is too small... overriding")
×
838
                    self.final_time = self.record_time + 60.0/self.problem.rpm
×
839
                    self.fprint("         New Final Time: {:1.2f} s".format(self.final_time))
×
840
            elif self.record_time == "last":
1✔
841
                self.record_time = self.final_time
×
842
            self.fprint("Recording Time: %.1f" % (self.record_time))
1✔
843
            # self.record_time = 250.0
844
            # self.record_time = self.final_time-2.0*saveInterval
845
        else:
846
            self.record_time = 0.0
1✔
847
        self.problem.record_time = self.record_time
1✔
848

849
        # Calculate what time to start the averaging process
850
        init_average = True
1✔
851
        if self.pseudo_steady:
1✔
852
            two_flowthrough_time = 2.0*(self.problem.dom.x_range[1] - self.problem.dom.x_range[0])/self.params['boundary_conditions']['HH_vel']
×
853
            average_start_time = two_flowthrough_time
×
854
            self.fprint('Start averaging after two flow-throughs, or %.1f seconds' % (two_flowthrough_time))
×
855
        else:
856
            average_start_time = 5.0
1✔
857

858
        # ================================================================
859

860
        # Start a counter for the number of saved files
861
        saveCount = 0
1✔
862
        save_next_timestep = False
1✔
863

864
        # Generate file pointers for saved output
865
        # FIXME: This should use the .save method
866
        # fp = []
867
        # fp.append(File("%s/timeSeries/velocity.pvd" % (self.problem.dom.params.folder)))
868
        # fp.append(File("%s/timeSeries/pressure.pvd" % (self.problem.dom.params.folder)))
869
        # fp.append(File("%s/timeSeries/nu_T.pvd" % (self.problem.dom.params.folder)))
870

871
        # if "turbine_force" in self.params.output:
872
        #     fp.append(File("%s/timeSeries/turbineForce.pvd" % (self.problem.dom.params.folder)))
873

874
        # Save first timestep (create file pointers for first call)
875
        self.SaveTimeSeries(self.simTime, 0.0)
1✔
876

877
        self.fprint("Saving Input Data",special="header")
1✔
878
        if "mesh" in self.params.output:
1✔
879
            self.problem.dom.Save(val=self.iter_val)
1✔
880
        if "initial_guess" in self.params.output:
1✔
881
            self.problem.bd.SaveInitialGuess(val=self.iter_val)
1✔
882
        if "height" in self.params.output and self.problem.dom.dim == 3:
1✔
883
            self.problem.bd.SaveHeight(val=self.iter_val)
1✔
884
        if "turbine_force" in self.params.output:
1✔
885
            self.problem.farm.save_functions(val=self.iter_val)
1✔
886

887
        self.fprint("Finished",special="footer")
1✔
888

889
        # ================================================================
890

891
        self.fprint("")
1✔
892
        self.fprint("Calculating Boundary Conditions")
1✔
893

894
        # FIXME: This should use the boundary information in self.problem.bd.bcs
895
        # bcu, bcp = self.GetBoundaryConditions(0.0)
896
        self.problem.dom.RecomputeBoundaryMarkers(0.0)
1✔
897

898
        # ================================================================
899

900
        self.fprint("Assembling time-independent matrices")
1✔
901

902
        # Assemble left-hand side matrices
903
        A1 = assemble(self.problem.a1)
1✔
904
        A2 = assemble(self.problem.a2)
1✔
905
        A3 = assemble(self.problem.a3)
1✔
906

907
        # Apply boundary conditions to matrices
908
        [bc.apply(A1) for bc in self.problem.bd.bcu]
1✔
909
        [bc.apply(A2) for bc in self.problem.bd.bcp]
1✔
910

911
        # Assemble right-hand side vector
912
        b1 = assemble(self.problem.L1)
1✔
913
        b2 = assemble(self.problem.L2)
1✔
914
        b3 = assemble(self.problem.L3)
1✔
915

916
        # Apply bounday conditions to vectors
917
        [bc.apply(b1) for bc in self.problem.bd.bcu]
1✔
918
        [bc.apply(b2) for bc in self.problem.bd.bcp]
1✔
919

920
        timing = np.zeros(5)
1✔
921

922
        # sol = ['cg', 'bicgstab', 'gmres']
923
        # pre = ['amg', 'hypre_amg', 'hypre_euclid', 'hypre_parasails', 'jacobi', 'petsc_amg', 'sor']
924

925
        # cl_iterator = int(self.params['solver']['cl_iterator'])
926

927
        # if cl_iterator > 0:
928
        #     ind = cl_iterator-1
929
        #     sol_choice = sol[int(np.floor(ind/7))]
930
        #     pre_choice = pre[int(ind%7)]
931

932
        # else:
933
        #     sol_choice = 'gmres'
934
        #     pre_choice = 'default'
935

936
        solver_1 = PETScKrylovSolver('gmres', 'jacobi')
1✔
937
        # solver_1 = PETScKrylovSolver('gmres', 'default')
938
        # solver_1 = PETScKrylovSolver('default', 'default')
939
        solver_1.set_operator(A1)
1✔
940

941
        solver_2 = PETScKrylovSolver('gmres', 'petsc_amg')
1✔
942
        # solver_2 = PETScKrylovSolver('gmres', 'hypre_amg')
943
        # solver_2 = PETScKrylovSolver('default', 'default')
944
        solver_2.set_operator(A2)
1✔
945

946
        solver_3 = PETScKrylovSolver('cg', 'jacobi')
1✔
947
        # solver_3 = PETScKrylovSolver('gmres', 'default')
948
        # solver_3 = PETScKrylovSolver('default', 'default')
949
        solver_3.set_operator(A3)
1✔
950

951
        pr = cProfile.Profile()
1✔
952

953
        # ================================================================
954

955
        self.fprint('Turbine Parameters', special='header')
1✔
956
        self.fprint('Hub Height: %.1f' % (self.problem.farm.turbines[0].HH))
1✔
957
        self.fprint('Yaw: %.4f' % (self.problem.farm.turbines[0].yaw))
1✔
958
        self.fprint('Radius: %.1f' % (self.problem.farm.turbines[0].radius))
1✔
959
        self.fprint('', special='footer')
1✔
960

961
        self.fprint("Solving",special="header")
1✔
962
        self.fprint("Sim Time | Next dt | U_max")
1✔
963
        self.fprint("--------------------------")
1✔
964

965
        def save_adj(adj):
1✔
966
            # print(len(self.adj_time_list),self.adj_time_iter)
967
            if self.adj_time_iter < len(self.adj_time_list):
×
968
                t = np.flip(self.adj_time_list)[self.adj_time_iter]
×
969
                # print(t,t % self.save_interval)
970
                if t % self.save_interval == 0:
×
971
                    adj.rename("adjoint","adjoint")
×
972
                    self.adj_file.write(adj,t)
×
973
            self.adj_time_iter += 1
×
974

975
        # Initialize need loop objects
976
        start = time.time()
1✔
977
        # self.problem.dt_sum = 1.0
978
        self.problem.dt_sum = 0.0
1✔
979
        J_old = 0
1✔
980
        J_diff_old = 100000
1✔
981
        min_count = 0
1✔
982
        simIter = 0
1✔
983
        stable = False
1✔
984

985
        if self.problem.farm.turbines[0].type == "line" or self.problem.farm.turbines[0].type == "dolfin_line":
1✔
986
            tip_speed = self.problem.farm.turbines[0].rpm*2.0*np.pi*self.problem.farm.turbines[0].radius/60.0
1✔
987
        else:
988
            tip_speed = 0.0
×
989

990
        # self.problem.alm_power_sum = 0.0
991

992
        while not stable and self.simTime < self.final_time:
1✔
993

994
            # add a fake block that allows us to update the control while dolfin_adjoint is doing it's thing
995
            # Need to allow processes which don't own the above points to fail gracefully
996
            # try:
997
            #     print(f"u(0, 0,150):   {self.problem.u_k([0.0, 0.0,150.0])}")
998
            # except:
999
            #     pass
1000
            # try:
1001
            #     print(f"u(0, 0,210):   {self.problem.u_k([0.0, 0.0,210.0])}")
1002
            # except:
1003
            #     pass
1004
            # try:
1005
            #     print(f"u(0,60,150):   {self.problem.u_k([0.0,60.0,150.0])}")
1006
            # except:
1007
            #     pass
1008
            # print(f"max(u):        {self.problem.u_k.vector().max()}")
1009
            # print(f"min(u):        {self.problem.u_k.vector().min()}")
1010
            # print(f"integral(u_x): {assemble(self.problem.u_k[0]*dx)}")
1011
            self.J = self.control_updater(self.J, self.problem, time=self.simTime)
1✔
1012

1013
            self.problem.bd.UpdateVelocity(self.simTime)
1✔
1014

1015
            # Record the "old" max velocity (before this update)
1016
            u_max_k1 = max(tip_speed, self.problem.u_k.vector().max())
1✔
1017

1018
            # Step 1: Tentative velocity step
1019
            tic = time.time()
1✔
1020
            # solve(self.problem.a1==self.problem.L1, self.problem.u_k, bcs=self.problem.bd.bcu)
1021
            # solve(self.problem.a1==self.problem.L1, self.problem.u_k, bcs=self.problem.bd.bcu, solver_parameters={"linear_solver": "gmres","preconditioner": "jacobi"})
1022

1023
            b1 = assemble(self.problem.L1, tensor=b1)
1✔
1024
            [bc.apply(b1) for bc in self.problem.bd.bcu]
1✔
1025
            if self.optimizing:
1✔
1026
                # solve(A1, self.problem.u_k.vector(), b1, 'gmres', 'default',adj_cb=save_adj)
1027
                # solve(A1, self.problem.u_k.vector(), b1, 'gmres', 'default')
1028
                solver_1.solve(self.problem.u_k.vector(), b1)
1✔
1029
            else:
1030
                # solve(A1, self.problem.u_k.vector(), b1, 'gmres', 'default')
1031
                solver_1.solve(self.problem.u_k.vector(), b1)
1✔
1032
            # print("uk("+repr(self.simTime)+")   = "+repr(np.mean(self.problem.u_k.vector()[:])))
1033
            # print("assemble(func*dx): " + repr(float(assemble(inner(self.problem.u_k,self.problem.u_k)*dx))))
1034
            toc = time.time()
1✔
1035
            timing[0] += toc - tic
1✔
1036

1037
            # Step 2: Pressure correction step
1038
            tic = time.time()
1✔
1039
            # solve(self.problem.a2==self.problem.L2, self.problem.p_k, bcs=self.problem.bd.bcp)
1040
            # solve(self.problem.a2==self.problem.L2, self.problem.p_k, bcs=self.problem.bd.bcp, solver_parameters={"linear_solver": "gmres","preconditioner": "petsc_amg"})
1041
            
1042
            b2 = assemble(self.problem.L2, tensor=b2)
1✔
1043
            [bc.apply(b2) for bc in self.problem.bd.bcp]
1✔
1044
            if self.optimizing:
1✔
1045
                # solve(A2, self.problem.p_k.vector(), b2, 'gmres', 'hypre_amg')
1046
                solver_2.solve(self.problem.p_k.vector(), b2)
1✔
1047
            else:
1048
                solver_2.solve(self.problem.p_k.vector(), b2)
1✔
1049
            # print("uk("+repr(self.simTime)+")   = "+repr(np.mean(self.problem.p_k.vector()[:])))
1050
            # print("assemble(func*dx): " + repr(float(assemble(inner(self.problem.p_k,self.problem.p_k)*dx))))
1051
            toc = time.time()
1✔
1052
            timing[1] += toc - tic
1✔
1053

1054
            # Step 3: Velocity correction step
1055
            tic = time.time()
1✔
1056
            # solve(self.problem.a3==self.problem.L3, self.problem.u_k)
1057
            # solve(self.problem.a3==self.problem.L3, self.problem.u_k, solver_parameters={"linear_solver": "cg","preconditioner": "jacobi"})
1058
            
1059
            b3 = assemble(self.problem.L3, tensor=b3)
1✔
1060
            if self.optimizing:
1✔
1061
                # solve(A3, self.problem.u_k.vector(), b3, 'gmres', 'default')
1062
                solver_3.solve(self.problem.u_k.vector(), b3)
1✔
1063
            else:
1064
                solver_3.solve(self.problem.u_k.vector(), b3)
1✔
1065
            # print("uk("+repr(self.simTime)+")   = "+repr(np.mean(self.problem.u_k.vector()[:])))
1066
            # print("assemble(func*dx): " + repr(float(assemble(inner(self.problem.u_k,self.problem.u_k)*dx))))
1067
            toc = time.time()
1✔
1068
            timing[2] += toc - tic
1✔
1069

1070
            # Old <- New update step
1071
            self.problem.u_k2.assign(self.problem.u_k1)
1✔
1072
            self.problem.u_k1.assign(self.problem.u_k)
1✔
1073
            self.problem.p_k1.assign(self.problem.p_k)
1✔
1074

1075
            # Record the updated max velocity
1076
            u_max = max(tip_speed, self.problem.u_k.vector().max())
1✔
1077

1078
            # Update the simulation time
1079
            self.simTime_prev = self.simTime
1✔
1080
            self.simTime += self.problem.dt
1✔
1081

1082
            # Compute Reynolds Stress
1083
            if 'KE_entrainment' in self.objective_type.keys():
1✔
1084
                if self.simTime >= self.u_avg_time and self.simTime < self.record_time:
×
1085
                    self.problem.uk_sum.assign(self.problem.uk_sum+self.problem.dt_c*self.problem.u_k)
×
1086
                elif self.simTime >= self.record_time:
×
1087
                    self.problem.vertKE = (self.problem.u_k[0]-self.problem.uk_sum[0]/(self.record_time-self.u_avg_time))*(self.problem.u_k[2]-self.problem.uk_sum[2]/(self.record_time-self.u_avg_time))*(self.problem.uk_sum[0]/(self.record_time-self.u_avg_time))
×
1088
            
1089
            if self.save_all_timesteps:
1✔
1090
                self.SaveTimeSeries(self.simTime,simIter)
×
1091
            elif save_next_timestep:
1✔
1092
                # Read in new inlet values
1093
                # bcu = self.updateInletVelocityFromFile(saveCount, bcu)
1094
                
1095
                # Clean up self.simTime to avoid accumulating round-off error
1096
                saveCount += 1
1✔
1097
                self.simTime = self.save_interval*saveCount
1✔
1098

1099
                # Save output files
1100
                # self.SaveTimeSeries(fp, self.simTime)
1101
                self.SaveTimeSeries(self.simTime,simIter)
1✔
1102

1103
            # Adjust the timestep size, dt, for a balance of simulation speed and stability
1104
            save_next_timestep = self.AdjustTimestepSize(save_next_timestep, self.save_interval, self.simTime, u_max, u_max_k1)
1✔
1105

1106
            # Update the turbine force
1107
            tic = time.time()
1✔
1108
            if self.problem.farm.turbines[0].type == "line" or self.problem.farm.turbines[0].type == "dolfin_line":
1✔
1109

1110
                self.problem.alm_power = 0.0
1✔
1111
                # pass
1112

1113
                # t1 = time.time()
1114
                pr.enable()
1✔
1115
                if 'dolfin' in self.problem.farm.turbines[0].type:
1✔
1116
                    self.problem.ComputeTurbineForceTerm(self.problem.u_k, TestFunction(self.problem.fs.V), self.problem.dom.inflow_angle, simTime=self.simTime, simTime_prev=self.simTime_prev, dt=self.problem.dt)
1✔
1117
                else:
1118
                    # updated method from dev, is this necessary? this may not work if there are self.farm.too_many_turbines
1119
                    self.problem.farm.update_turbine_force(self.problem.u_k, self.problem.dom.inflow_angle, self.problem.fs, simTime=self.simTime, simTime_prev=self.simTime_prev, dt=self.problem.dt)
1✔
1120
                    # new_tf = self.problem.ComputeTurbineForce(self.problem.u_k, self.problem.dom.inflow_angle, simTime=self.simTime, simTime_prev=self.simTime_prev, dt=self.problem.dt)
1121
                    # self.problem.tf.assign(new_tf)
1122
                pr.disable()
1✔
1123

1124
                # # t2 = time.time()
1125
                # # print(t2-t1)
1126

1127
                # Power [=] N*m*rads/s 
1128
                # self.problem.alm_power = self.problem.rotor_torque*(2.0*np.pi*self.problem.rpm/60.0)
1129
                # self.problem.alm_power_dolfin = self.problem.rotor_torque_dolfin*(2.0*np.pi*self.problem.rpm/60.0)
1130
                
1131
                # # self.problem.alm_power_sum += self.problem.alm_power*self.problem.dt
1132
                # # self.problem.alm_power_average = self.problem.alm_power_sum/self.simTime
1133

1134
                # self.problem.alm_power_dolfin = self.problem.farm.compute_power(self.problem.u_k, self.problem.dom.inflow_angle)
1135
                # output_str = 'Rotor Power (dolfin, solver): %s MW' % (self.problem.alm_power_dolfin/1.0e6)
1136
                # self.fprint(output_str)
1137

1138
            else:
1139
                # This is a hack to avoid errors when using something other than ALM
1140
                # e.g., actuator disks
1141
                self.problem.alm_power = np.zeros(self.problem.farm.numturbs)
×
1142
                self.problem.alm_power_dolfin = np.zeros(self.problem.farm.numturbs)
×
1143

1144
                # exit()
1145
            toc = time.time()
1✔
1146
            timing[3] += toc - tic
1✔
1147

1148
            if self.simTime > average_start_time:
1✔
1149
                if init_average:
×
1150

1151
                    convergence_history = []
×
1152

1153
                    average_vel_sum = Function(self.problem.fs.V)
×
1154
                    average_vel_1 = Function(self.problem.fs.V)
×
1155
                    average_vel_2 = Function(self.problem.fs.V)
×
1156

1157
                    average_vel_sum.vector()[:] = self.problem.u_k1.vector()[:]*self.problem.dt_previous
×
1158
                    average_vel_1.vector()[:] = average_vel_sum.vector()[:]/(self.simTime-average_start_time)
×
1159
                    average_vel_2.vector()[:] = 0.0
×
1160

1161
                    average_power = self.problem.alm_power*self.problem.dt
×
1162

1163
                    init_average = False
×
1164
                else:
1165
                    average_vel_sum.vector()[:] += self.problem.u_k1.vector()[:]*self.problem.dt_previous
×
1166

1167
                    average_vel_2.vector()[:] = average_vel_1.vector()[:]
×
1168
                    average_vel_1.vector()[:] = average_vel_sum.vector()[:]/(self.simTime-average_start_time)
×
1169

1170
                    norm_diff = norm(average_vel_2.vector() - average_vel_1.vector(), 'linf')
×
1171
                    self.fprint('Change Between Steps (norm)'+ repr(norm_diff))
×
1172

1173

1174
                    if norm_diff < 1e-4:
×
1175
                        # If the average is no longer changing, stop this run
1176
                        convergence_history.append(1)
×
1177

1178
                        # Only mark "stable" if the last 2 tests have passed
1179
                        if convergence_history[-1] == 1 and convergence_history[-2] == 1:
×
1180
                            stable = True
×
1181
                    else:
1182
                        convergence_history.append(0)
×
1183

1184
                    average_power += self.problem.alm_power*self.problem.dt
×
1185

1186

1187
            # # Adjust the timestep size, dt, for a balance of simulation speed and stability
1188
            # save_next_timestep = self.AdjustTimestepSize(save_next_timestep, self.save_interval, self.simTime, u_max, u_max_k1)
1189

1190
            if self.save_objective or (self.optimizing and self.simTime >= self.record_time and not self.pseudo_steady):
1✔
1191
                J_next = self.EvaluateObjective()
1✔
1192

1193
            # Calculate the objective function
1194
            if self.optimizing and self.simTime >= self.record_time and not self.pseudo_steady:
1✔
1195

1196
                # Append the current time step for post production
1197
                self.adj_time_list.append(self.simTime)
1✔
1198

1199
                self.J += float(self.problem.dt)*J_next
1✔
1200
                self.problem.dt_sum += self.problem.dt 
1✔
1201
                J_new = float(self.J/self.problem.dt_sum)
1✔
1202

1203
                ### TODO, replace this with an actual stabilization criteria such as relative difference tolerance
1204
                # Check the change in J with respect to time and check if we are "stable" i.e. hit the required number of minimums
1205
                J_diff = J_new-J_old
1✔
1206
                # if J_diff_old <= 0 and J_diff > 0 and self.min_total:
1207
                #     if min_count == self.min_total:
1208
                #         stable = True
1209
                #     else:
1210
                #         min_count += 1
1211
                # J_diff_old = J_diff
1212
                J_old = J_new
1✔
1213

1214
                # Another stable checking method that just looks at the difference
1215
                # if abs(J_diff) <= 0.001:
1216
                #     stable = True
1217

1218
                self.fprint("Current Objective Value: "+repr(float(self.J/self.problem.dt_sum)))
1✔
1219
                self.fprint("Change in Objective    : "+repr(float(J_diff)))
1✔
1220

1221

1222

1223
            # to only call the power functional once, check if a) the objective is the power, b) that we are before record time
1224
            if self.save_power:
1✔
1225
                self.EvaulatePowerFunctional()
1✔
1226

1227
            # After changing timestep size, A1 must be reassembled
1228
            # FIXME: This may be unnecessary (or could be sped up by changing only the minimum amount necessary)
1229

1230
            tic = time.time()
1✔
1231
            A1 = assemble(self.problem.a1, tensor=A1)
1✔
1232
            [bc.apply(A1) for bc in self.problem.bd.bcu]
1✔
1233
            solver_1.set_operator(A1)
1✔
1234
            toc = time.time()
1✔
1235
            timing[4] += toc - tic
1✔
1236

1237
            # Print some solver statistics
1238
            self.fprint("%8.2f | %7.2f | %5.2f" % (self.simTime, self.problem.dt, u_max))
1✔
1239
            simIter+=1
1✔
1240

1241
        if self.pseudo_steady:
1✔
1242
            self.J = self.EvaluateObjective()
×
1243

1244
        elif (self.optimizing or self.save_objective):
1✔
1245
            # if self.problem.dt_sum > 0.0:
1246
            self.J = self.J/float(self.problem.dt_sum)
1✔
1247

1248

1249
        if self.simTime > average_start_time:
1✔
1250
            average_vel_sum.vector()[:] = average_vel_sum.vector()[:]/(self.simTime-average_start_time)
×
1251
            # fp = File('./output/%s/average_vel_sum.pvd' % (self.params.name))
1252
            fp = File(f"{self.params.folder}timeSeries/average_vel_sum.pvd")
×
1253
            average_vel_sum.rename('average_vel_sum', 'average_vel_sum')
×
1254
            fp << average_vel_sum
×
1255

1256
            average_power = average_power/(self.simTime-average_start_time)
×
1257
            # self.fprint('AVERAGE Rotor Power: %.6f MW' % (average_power/1e6))
1258
            try:
×
1259
                output_str = 'AVERAGE Rotor Power: %s MW' % (np.array2string(average_power/1.0e6, precision=9, separator=', '))
×
1260
            except:
×
1261
                output_str = 'AVERAGE Rotor Power: %s MW' % (average_power)
×
1262
            self.fprint(output_str)
×
1263

1264
        # add a fake block that allows us to update the control while dolfin_adjoint is doing it's thing
1265
        # try:
1266
        #     print(f"u(0, 0,150):   {self.problem.u_k([0.0, 0.0,150.0])}")
1267
        # except:
1268
        #     pass
1269
        # try:
1270
        #     print(f"u(0, 0,210):   {self.problem.u_k([0.0, 0.0,210.0])}")
1271
        # except:
1272
        #     pass
1273
        # try:
1274
        #     print(f"u(0,60,150):   {self.problem.u_k([0.0,60.0,150.0])}")
1275
        # except:
1276
        #     pass
1277

1278
        # print(f"max(u):        {self.problem.u_k.vector().max()}")
1279
        # print(f"min(u):        {self.problem.u_k.vector().min()}")
1280
        # print(f"integral(u_x): {assemble(self.problem.u_k[0]*dx)}")
1281
        self.J = self.control_updater(self.J, self.problem, time=self.simTime)
1✔
1282
        stop = time.time()
1✔
1283

1284
        self.fprint('================================================================')
1✔
1285
        # self.fprint('Solver:              %s' % (sol_choice))
1286
        # self.fprint('Preconditioner:      %s' % (pre_choice))
1287
        total_time = stop - start
1✔
1288
        self.fprint('Assembling A1:       %.2f sec (%.1f%%)' % (timing[4], 100.0*timing[4]/total_time))
1✔
1289
        self.fprint('Tentative Velocity:  %.2f sec (%.1f%%)' % (timing[0], 100.0*timing[0]/total_time))
1✔
1290
        self.fprint('Pressure Correction: %.2f sec (%.1f%%)' % (timing[1], 100.0*timing[1]/total_time))
1✔
1291
        self.fprint('Velocity Update:     %.2f sec (%.1f%%)' % (timing[2], 100.0*timing[2]/total_time))
1✔
1292
        self.fprint('ALM Calculation:     %.2f sec (%.1f%%)' % (timing[3], 100.0*timing[3]/total_time))
1✔
1293
        self.fprint('================================================================')
1✔
1294
        self.fprint("Finished",special="footer")
1✔
1295
        self.fprint("Solve Complete: {:1.2f} s".format(stop-start),special="footer")
1✔
1296

1297
        # if self.params.rank == 0:
1298
        #     # pr.print_stats(25, sort = 'time')
1299
        #     ps = pstats.Stats(pr).strip_dirs().sort_stats('cumulative')
1300
        #     ps.print_stats(50)
1301
        #     pr.dump_stats('%s/profiling_alm.prof' % (self.params.folder))
1302

1303

1304
    # ================================================================
1305
    @no_annotations
1✔
1306
    def SaveTimeSeries(self, simTime, simIter=None):
1✔
1307

1308
        self.DebugOutput(simTime,simIter)
1✔
1309
        ### TODO THIS NEED TO BE CLEAN TO ACCOUNT FOR DISKS
1310

1311
        if self.problem.farm.turbines[0].type == "line":
1✔
1312
            if hasattr(self.problem,"tf_save"):
1✔
1313
                self.problem.tf_save.vector()[:] = 0
1✔
1314
                for fun in self.problem.farm.turbines:
1✔
1315
                    self.problem.tf_save.vector()[:] = self.problem.tf_save.vector()[:] + fun.tf.vector()[:]
1✔
1316
            else:
1317
                self.problem.tf_save = Function(self.problem.fs.V)
1✔
1318
                for fun in self.problem.farm.turbines:
1✔
1319
                    self.problem.tf_save.vector()[:] = self.problem.tf_save.vector()[:] + fun.tf.vector()[:]
1✔
1320
        else:
1321
            if hasattr(self.problem,"tf_save"):
1✔
1322
                self.problem.tf_save = project(sum(self.problem.farm.tf_list), self.problem.fs.V, solver_type='cg')
1✔
1323
            else:
1324
                self.problem.tf_save = Function(self.problem.fs.V)
1✔
1325
                self.problem.tf_save = project(sum(self.problem.farm.tf_list), self.problem.fs.V, solver_type='cg')
1✔
1326

1327

1328
        if self.first_save:
1✔
1329
            self.velocity_file = self.params.Save(self.problem.u_k,"velocity",subfolder="timeSeries/",val=simTime)
1✔
1330
            self.pressure_file   = self.params.Save(self.problem.p_k,"pressure",subfolder="timeSeries/",val=simTime)
1✔
1331
            self.turb_force_file   = self.params.Save(self.problem.tf_save,"turbine_force",subfolder="timeSeries/",val=simTime)
1✔
1332
            # if self.optimizing:
1333
                # self.adj_tape_file = XDMFFile(self.params.folder+"timeSeries/global_adjoint.xdmf")
1334
                # self.problem.u_k1.assign(Marker(self.problem.u_k,simTime,self.adj_tape_file))
1335
            self.first_save = False
1✔
1336
        else:
1337
            self.params.Save(self.problem.u_k,"velocity",subfolder="timeSeries/",val=simTime,file=self.velocity_file)
1✔
1338
            self.params.Save(self.problem.p_k,"pressure",subfolder="timeSeries/",val=simTime,file=self.pressure_file)
1✔
1339
            self.params.Save(self.problem.tf_save,"turbine_force",subfolder="timeSeries/",val=simTime,file=self.turb_force_file)
1✔
1340
            # if self.optimizing:
1341
                # self.problem.u_k1.assign(Marker(self.problem.u_k,simTime,self.adj_tape_file))
1342

1343
        # # Save velocity files (pointer in fp[0])
1344
        # self.problem.u_k.rename('Velocity', 'Velocity')
1345
        # fp[0] << (self.problem.u_k, simTime)
1346

1347
        # # Save pressure files (pointer in fp[1])
1348
        # self.problem.p_k.rename('Pressure', 'Pressure')
1349
        # fp[1] << (self.problem.p_k, simTime)
1350

1351
        # # Save eddy viscosity files (pointer in fp[2])
1352
        # # nu_T_val = project(self.problem.nu_T, self.problem.fs.Q, solver_type='gmres')
1353
        # # nu_T_val.rename('nu_T', 'nu_T')
1354
        # # fp[2] << (nu_T_val, simTime)
1355

1356
        # # Save turbine force files (pointer in fp[3])
1357
        # # if "turbine_force" in self.params.output:
1358

1359
        # workaround = False
1360

1361
        # if workaround:
1362
        #     tf_value = project(self.problem.tf, self.problem.fs.V, solver_type='gmres')
1363
        #     tf_value.rename('Turbine_Force', 'Turbine_Force')
1364
        #     fp[3] << (tf_value, simTime)
1365
        # else:
1366
        #     self.problem.tf.rename('Turbine_Force', 'Turbine_Force')
1367
        #     fp[3] << (self.problem.tf, simTime)
1368

1369

1370
    # ================================================================
1371

1372
    def AdjustTimestepSize(self, save_next_timestep, saveInterval, simTime, u_max, u_max_k1):
1✔
1373
        # Save the old dt for reference
1374
        self.problem.dt_previous = self.problem.dt
1✔
1375

1376
        # Set the CFL target (0.2 is a good value for stability and speed, YMMV)
1377
        # cfl_target = 0.5
1378
        cfl_target = 1.0
1✔
1379
        cfl_target = float(self.problem.params["solver"]["cfl_target"])
1✔
1380

1381
        # Enforce a minimum timestep size
1382
        dt_min = 0.01
1✔
1383

1384
        # Calculate the change in velocity using a first-order, backward difference
1385
        dudt = u_max - u_max_k1
1✔
1386

1387
        # Calculate the projected velocity
1388
        u_max_projected = u_max + dudt
1✔
1389

1390
        # Calculate the ideal timestep size (ignore file output considerations for now)
1391
        dt_new = cfl_target * self.problem.dom.global_hmin / u_max_projected
1✔
1392

1393
        # Move to larger dt slowly (smaller dt happens instantly)
1394
        if dt_new > self.problem.dt:
1✔
1395
            # Amount of new dt to use: 0 = none, 1 = all
1396
            SOR = 0.5
1✔
1397
            dt_new = SOR*dt_new + (1.0-SOR)*self.problem.dt
1✔
1398

1399
        # dt_new = dt_new/2.0
1400

1401
        # Calculate the time remaining until the next file output
1402
        time_remaining = saveInterval - (simTime % saveInterval)
1✔
1403

1404
        # If the new timestep would jump past a save point, modify the new timestep size
1405
        if not save_next_timestep and dt_new + dt_min >= time_remaining:
1✔
1406
            dt_new = time_remaining
1✔
1407
            save_next_timestep = True
1✔
1408
        else:
1409
            save_next_timestep = False
1✔
1410

1411
        # dt_new = 0.04
1412
        if self.params.num_procs > 1:
1✔
1413
            dt_new_vec = np.zeros(self.params.num_procs, dtype=np.float64)
×
1414
            dt_new = np.array(dt_new, dtype=np.float64)
×
1415
            self.params.comm.Allgather(dt_new, dt_new_vec)
×
1416
            dt_new = np.amin(dt_new_vec)
×
1417

1418
        # print('Rank %d dt = %.15e' % (self.params.rank, dt_new))
1419

1420
        # Update both the Python variable and FEniCS constant
1421
        self.problem.dt = dt_new
1✔
1422
        self.problem.dt_c.assign(dt_new)
1✔
1423

1424

1425
        # float(self.problem.dt_c) # to get the regular ol' variable
1426

1427
        return save_next_timestep
1✔
1428

1429
    # ================================================================
1430

1431
    def UpdateActuatorLineForce(self, simTime):
1✔
1432

1433
        def rot_x(theta):
×
1434
            Rx = np.array([[1, 0, 0],
×
1435
                           [0, np.cos(theta), -np.sin(theta)],
1436
                           [0, np.sin(theta), np.cos(theta)]])
1437

1438
            return Rx
×
1439

1440
        def rot_y(theta):
×
1441
            Ry = np.array([[np.cos(theta), 0, np.sin(theta)],
×
1442
                           [0, 1, 0],
1443
                           [-np.sin(theta), 0, np.cos(theta)]])
1444
            
1445
            return Ry
×
1446

1447
        def rot_z(theta):
×
1448
            Rz = np.array([[np.cos(theta), -np.sin(theta), 0],
×
1449
                           [np.sin(theta), np.cos(theta), 0],
1450
                           [0, 0, 1]])
1451
            
1452
            return Rz
×
1453

1454
        #================================================================
1455
        # Get Mesh Properties
1456
        #================================================================
1457

1458
        ndim = self.problem.dom.dim
×
1459

1460
        # Get the coordinates of the vector function space
1461
        coords = self.problem.fs.V.tabulate_dof_coordinates()
×
1462
        coords = np.copy(coords[0::self.problem.dom.dim, :])
×
1463

1464

1465
        # Resape a linear copy of the coordinates for every mesh point
1466
        coordsLinear = np.copy(coords.reshape(-1, 1))
×
1467

1468
        #================================================================
1469
        # Set Turbine and Fluid Properties
1470
        #================================================================
1471

1472
        # Set the density
1473
        rho = 1.0
×
1474

1475
        # Set the hub height
1476
        hub_height = self.problem.farm.HH[0] # For a SWIFT turbine
×
1477

1478
        # Get the hub-height velocity
1479
        u_inf = 8.0
×
1480

1481
        # Set the rotational speed of the turbine
1482
        RPM = 15.0
×
1483

1484
        # Set the yaw of the turbine
1485
        yaw = self.problem.farm.yaw[0]
×
1486

1487
        # Set the number of blades in the turbine
1488
        num_blades = 3
×
1489

1490
        # Blade length (turbine radius)
1491
        L = self.problem.farm.radius[0] # For a SWIFT turbine 27 m in diameter
×
1492

1493
        # Chord length
1494
        c = L/20.0
×
1495

1496
        # Width of Gaussian
1497
        # eps = 2.5*c
1498
        eps = 2.0*self.problem.dom.mesh.hmin()/np.sqrt(3)
×
1499

1500
        # print(self.problem.farm.x)
1501
        # print(self.problem.farm.y)
1502
        # print(self.problem.farm.HH)
1503
        # print(self.problem.farm.yaw)
1504
        # print(self.problem.farm.RD)
1505
        # print(self.problem.farm.radius)
1506

1507
        # Discretize each blade into separate nodes
1508
        num_actuator_nodes = 10
×
1509

1510
        #================================================================
1511
        # Set Derived Constants
1512
        #================================================================
1513

1514
        # Calculate the blade velocity
1515
        period = 60.0/RPM
×
1516
        tip_speed = np.pi*2.0*L*RPM/60.0
×
1517
        blade_vel = np.vstack((np.zeros(num_actuator_nodes),
×
1518
                               np.zeros(num_actuator_nodes),
1519
                               np.linspace(0.0, tip_speed, num_actuator_nodes)))
1520

1521
        # Set the initial angle of each blade
1522
        theta_vec = np.linspace(0.0, 2.0*np.pi, num_blades+1)
×
1523
        theta_vec = theta_vec[0:num_blades]
×
1524

1525
        # Calculate discrete node positions
1526
        rdim = np.linspace(0.0, L, num_actuator_nodes)
×
1527

1528
        # Calculate width of individual blade segment
1529
        w = rdim[1] - rdim[0]
×
1530

1531
        # Calculate an array describing the x, y, z position of each point
1532
        xblade = np.vstack((np.zeros(num_actuator_nodes),
×
1533
                            rdim,
1534
                            np.zeros(num_actuator_nodes)))
1535

1536
        #================================================================
1537
        # Begin Calculating Turbine Forces
1538
        #================================================================
1539

1540
        # Lift and drag coefficient (could be an array and you interpolate the value based on R)
1541
        # cl_dolf = Constant((np.linspace(1.5, 0.5, num_actuator_nodes)))
1542
        # cd_dolf = Constant((np.ones(num_actuator_nodes)))
1543
        # cl = cl_dolf.values()
1544
        # cd = cd_dolf.values()
1545

1546
        cl = np.linspace(0.0, 2.0, num_actuator_nodes) # Uncomment for controllability study
×
1547
        cd = np.linspace(2.0, 0.0, num_actuator_nodes)
×
1548
        # cl = np.linspace(2.0, 0.0, num_actuator_nodes) # Uncomment for controllability study
1549
        # cd = np.linspace(0.0, 2.0, num_actuator_nodes)
1550
        # cl = np.ones(num_actuator_nodes)
1551
        # cd = np.ones(num_actuator_nodes)
1552

1553

1554
        # Create space to hold the vector values
1555
        tf_vec = np.zeros(np.size(coords))
×
1556
        lift_force = np.zeros((np.shape(coords)[0], ndim))
×
1557
        drag_force = np.zeros((np.shape(coords)[0], ndim))
×
1558

1559
        # tf_lift_vec = np.zeros(np.size(coords))
1560
        # tf_drag_vec = np.zeros(np.size(coords))
1561

1562
        # Calculate the blade position based on current simTime and turbine RPM
1563
        theta_offset = simTime/period*2.0*np.pi
×
1564

1565
        # Treat each blade separately
1566
        for theta_0 in theta_vec:
×
1567
            theta = theta_0 + theta_offset
×
1568

1569
            # Generate a rotation matrix for this turbine blade
1570
            Rx = rot_x(theta)
×
1571
            Rz = rot_z(yaw)
×
1572

1573
            # Rotate the entire [x; y; z] matrix using this matrix, then shift to the hub height
1574
            xblade_rotated = np.dot(Rz, np.dot(Rx, xblade))
×
1575
            xblade_rotated[2, :] += hub_height
×
1576

1577
            # Tile the blade coordinates for every mesh point, [numGridPts*ndim x num_actuator_nodes]
1578
            xblade_rotated_full = np.tile(xblade_rotated, (np.shape(coords)[0], 1))
×
1579

1580
            # Subtract and square to get the dx^2 values in the x, y, and z directions
1581
            dx_full = (coordsLinear - xblade_rotated_full)**2
×
1582

1583
            # Add together to get |x^2 + y^2 + z^2|^2
1584
            dist2 = dx_full[0::ndim] + dx_full[1::ndim] + dx_full[2::ndim]
×
1585

1586
            # Set if using local velocity around inidividual nodes
1587
            using_local_velocity = False
×
1588
        
1589
            if using_local_velocity:
×
1590
                # Generate the fluid velocity from the actual node locations in the flow
1591
                u_fluid = np.zeros((3, num_actuator_nodes))
×
1592
                
1593
                for k in range(num_actuator_nodes):
×
1594
                    u_fluid[:, k] = self.problem.u_k1(xblade_rotated[0, k],
×
1595
                                                      xblade_rotated[1, k],
1596
                                                      xblade_rotated[2, k])
1597
                                    
1598
            else:
1599
                # Generate the fluid velocity analytically using the hub height velocity
1600
                # u_inf_vec = u_inf*np.ones(num_actuator_nodes)
1601
                
1602
                # u_fluid = np.vstack((u_inf_vec,
1603
                #                      np.zeros(num_actuator_nodes),
1604
                #                      np.zeros(num_actuator_nodes)))
1605
                u_fluid = np.zeros((3, num_actuator_nodes))
×
1606
                
1607
                for k in range(num_actuator_nodes):
×
1608
                    u_fluid[0, k] = 8.0*(xblade_rotated[2, k]/hub_height)**0.18
×
1609

1610
            
1611
            # Rotate the blade velocity in the global x, y, z, coordinate system
1612
            blade_vel_rotated = np.dot(Rz, np.dot(Rx, -blade_vel))
×
1613
                            
1614
            # Form the total relative velocity vector (including velocity from rotating blade)
1615
            u_rel = u_fluid + blade_vel_rotated
×
1616
            
1617
            # Create unit vectors in the direction of u_rel
1618
            u_rel_mag = np.linalg.norm(u_rel, axis=0)
×
1619
            u_rel_mag[u_rel_mag < 1e-6] = 1e-6
×
1620
            u_unit = u_rel/u_rel_mag
×
1621
            
1622
            # Calculate the lift and drag forces using the relative velocity magnitude
1623
            lift = (0.5*cl*rho*c*w*u_rel_mag**2)/(eps**3 * np.pi**1.5)
×
1624
            drag = (0.5*cd*rho*c*w*u_rel_mag**2)/(eps**3 * np.pi**1.5)
×
1625
            
1626
            # Calculate the force at every mesh point due to every node [numGridPts x NumActuators]
1627
            nodal_lift = lift*np.exp(-dist2/eps**2)
×
1628
            nodal_drag = drag*np.exp(-dist2/eps**2)
×
1629
            
1630
            # Calculate a vector in the direction of the blade
1631
            blade_unit = xblade_rotated[:, -1] - np.array([0.0, 0.0, hub_height])  
×
1632
            
1633
            for k in range(num_actuator_nodes):
×
1634
                # The drag unit simply points opposite the relative velocity unit vector
1635
                drag_unit = -u_unit[:, k]
×
1636
                
1637
                # The lift is normal to the plane generated by the blade and relative velocity
1638
                lift_unit = np.cross(drag_unit, blade_unit)
×
1639
                lift_unit_mag = np.linalg.norm(lift_unit)
×
1640
                if lift_unit_mag < 1e-6:
×
1641
                    lift_unit_mag = 1e-6
×
1642
                lift_unit = lift_unit/lift_unit_mag
×
1643
                
1644
                vector_nodal_drag = np.outer(nodal_drag[:, k], drag_unit)
×
1645
                vector_nodal_lift = np.outer(nodal_lift[:, k], lift_unit)
×
1646

1647
                drag_force += vector_nodal_drag
×
1648
                lift_force += vector_nodal_lift
×
1649
                    
1650
            # The total turbine force is the sum of lift and drag effects
1651
        turbine_force = drag_force + lift_force
×
1652

1653
        for k in range(ndim):
×
1654
            tf_vec[k::ndim] = turbine_force[:, k]
×
1655
            # tf_lift_vec[k::ndim] += lift_force[:, k]
1656
            # tf_drag_vec[k::ndim] += drag_force[:, k]
1657

1658
        # Save the output
1659
        tf_vec[np.abs(tf_vec) < 1e-12] = 0.0
×
1660
        # tf_lift_vec[np.abs(tf_lift_vec) < 1e-12] = 0.0
1661
        # tf_drag_vec[np.abs(tf_drag_vec) < 1e-12] = 0.0
1662

1663
        self.problem.tf.vector()[:] = tf_vec
×
1664

1665
    # ================================================================
1666

1667
    def UpdateActuatorLineForceOld(self, simTime):
1✔
1668
        coords = self.problem.fs.V.tabulate_dof_coordinates()
×
1669
        coords = np.copy(coords[0::self.problem.dom.dim, :])
×
1670

1671
        # Set up the turbine geometry
1672
        num_blades = 3
×
1673
        theta_vec = np.linspace(0, 2.0*np.pi, num_blades+1)
×
1674
        theta_vec = theta_vec[0:num_blades]
×
1675

1676
        # print(theta_vec)
1677

1678
        # Lift and drag coefficient (could be an array and you interpolate the value based on R)
1679
        cl = 1.0
×
1680
        cd = 2.0
×
1681

1682
        rho = 1
×
1683

1684
        u_inf = 8
×
1685

1686
        # Blade length (turbine radius)
1687
        L = 13.5
×
1688

1689
        # Chord length
1690
        c = L/20
×
1691

1692
        # Number of blade evaluation sections
1693
        num_actuator_nodes = 50
×
1694
        rdim = np.linspace(0, L, num_actuator_nodes)
×
1695
        zdim = 0.0 + np.zeros(num_actuator_nodes)
×
1696
        xblade = np.vstack((np.zeros(num_actuator_nodes), rdim, zdim))
×
1697

1698
        # Width of individual blade segment
1699
        w = rdim[1] - rdim[0]
×
1700

1701
        # Width of Gaussian
1702
        eps = 2.5*c
×
1703

1704
        tf_vec = np.zeros(np.size(coords))
×
1705

1706

1707

1708

1709
        RPM = 15.0
×
1710
        period = 60.0/RPM
×
1711
        theta_offset = simTime/period*2.0*np.pi
×
1712

1713
        tip_speed = np.pi*2*L*RPM/60
×
1714

1715
        blade_vel = np.linspace(0.0, tip_speed, num_actuator_nodes)
×
1716

1717

1718
        constant_vel_mag = True
×
1719

1720
        if constant_vel_mag:
×
1721
            # Lift and drag force (calculated outside loop since cl, cd, u_inf, and c assumed constant)
1722
            lift = 0.5*cl*rho*u_inf**2*c*w
×
1723
            drag = 0.5*cd*rho*u_inf**2*c*w
×
1724

1725
            # Save time by moving calculation out of loop
1726
            L1 = lift/(eps**3 * np.pi**1.5)
×
1727
            D1 = drag/(eps**3 * np.pi**1.5)
×
1728
        else:
1729
            # Lift and drag force (calculated outside loop since cl, cd, u_inf, and c assumed constant)
1730
            u_inf = u_inf**2 + blade_vel**2
×
1731
            lift = 0.5*cl*rho*c*w*u_inf
×
1732
            drag = 0.5*cd*rho*c*w*u_inf
×
1733

1734
            # Save time by moving calculation out of loop
1735
            L1 = lift/(eps**3 * np.pi**1.5)
×
1736
            D1 = drag/(eps**3 * np.pi**1.5)
×
1737

1738

1739

1740
        for theta_0 in theta_vec:
×
1741
            theta = theta_0 + theta_offset
×
1742
            
1743
            # Create rotation matrix for this turbine blade
1744
            rotA = np.array([[1, 0, 0],
×
1745
                             [0, np.cos(theta), -np.sin(theta)],
1746
                             [0, np.sin(theta), np.cos(theta)]])
1747

1748
            # Rotate the entire [x; y; z] matrix using this matrix
1749
            xblade_rotated = np.dot(rotA, xblade)
×
1750
            xblade_rotated[2, :] += 32.1
×
1751

1752
            use_vectorized_calculation = True
×
1753

1754
            if use_vectorized_calculation:
×
1755
                coordsLinear = np.copy(coords.reshape(-1, 1))
×
1756

1757
                xblade_rotated_full = np.tile(xblade_rotated, (np.shape(coords)[0], 1))
×
1758

1759
                dx_full = (coordsLinear - xblade_rotated_full)**2
×
1760

1761
                dist2 = dx_full[0::self.problem.dom.dim] + \
×
1762
                dx_full[1::self.problem.dom.dim] + \
1763
                dx_full[2::self.problem.dom.dim]
1764

1765
                total_dist2_lift = np.sum(L1*np.exp(-dist2/eps**2), axis = 1)
×
1766
                total_dist2_drag = np.sum(D1*np.exp(-dist2/eps**2), axis = 1)
×
1767

1768
                tf_vec[0::self.problem.dom.dim] += -total_dist2_drag
×
1769
                tf_vec[1::self.problem.dom.dim] += total_dist2_lift*np.sin(theta)
×
1770
                tf_vec[2::self.problem.dom.dim] += -total_dist2_lift*np.cos(theta)
×
1771

1772
            else:
1773
                for k, x in enumerate(coords):
×
1774
                    # Flip row into column
1775
                    xT = x.reshape(-1, 1)
×
1776

1777
                    # Subtract this 3x1 point from the 3xN array of rotated turbine segments
1778
                    dx = (xT - xblade_rotated)**2
×
1779
                    mag = np.sum(dx, axis = 0)
×
1780

1781
                    # Add up the contribution from each blade segment
1782
                    lift_sum = np.sum(L1*np.exp(-mag/eps**2))
×
1783
                    drag_sum = np.sum(D1*np.exp(-mag/eps**2))
×
1784

1785
                    # Store the individual vector components using linear index
1786
                    tf_vec[3*k+0] += -drag_sum
×
1787
                    tf_vec[3*k+1] += lift_sum*np.sin(theta)
×
1788
                    tf_vec[3*k+2] += -lift_sum*np.cos(theta)
×
1789
                
1790
        tf_vec[np.abs(tf_vec) < 1e-12] = 0.0
×
1791

1792
        self.problem.tf.vector()[:] = tf_vec
×
1793

1794
    # ================================================================
1795

1796
    def UpdateTurbineForce(self, simTime, turbsPerPlatform):
1✔
1797
        coords = self.problem.fs.V.tabulate_dof_coordinates()
×
1798
        coords = np.copy(coords[0::self.problem.dom.dim, :])
×
1799

1800
        # Pre allocate all numpy arrays and vectors
1801
        tf_array = np.zeros(np.shape(coords))
×
1802
        tf_vec = np.zeros(np.size(tf_array))
×
1803
        xs = np.zeros(np.shape(coords))
×
1804

1805
        # Radius of the two "arms" measured from the hinge
1806
        rad = 189.0
×
1807

1808
        if turbsPerPlatform == 1:
×
1809
            rad = 0.0
×
1810

1811
        # Angle defined between the two "arms"
1812
        phi = np.pi/3.0
×
1813

1814
        # Calculate the offset from the hinge to each turbine
1815
        xp_offset = rad*np.cos(phi/2.0)
×
1816
        yp_offset = rad*np.sin(phi/2.0)
×
1817

1818
        # delta_yaw = 0.0
1819

1820
        for k in range(self.problem.farm.numturbs):
×
1821
            # Position of the kth turbune
1822
            xpos = float(self.problem.farm.mx[k])
×
1823
            ypos = float(self.problem.farm.my[k])
×
1824
            
1825
            if self.problem.dom.dim == 2:
×
1826
                x0 = np.array([xpos, ypos])
×
1827
            else:
1828
                zpos = float(self.problem.farm.mz[k])
×
1829
                x0 = np.array([xpos, ypos, zpos])
×
1830

1831
            # Yaw, thickness, radius, and mass of the kth turbine
1832
            # If desired, shift each turbine by a constant amount
1833
            # delta_yaw = np.pi/4.0*np.sin(np.pi*(simTime/1000.0 + k/self.problem.farm.numturbs))
1834
            delta_yaw = 0.0
×
1835

1836
            yaw = float(self.problem.farm.myaw[k] + delta_yaw)
×
1837
            W = float(self.problem.farm.W[k]/2.0)
×
1838
            R = float(self.problem.farm.RD[k]/2.0)
×
1839
            ma = float(self.problem.farm.ma[k])
×
1840

1841
            # Create a rotation matrix for this yaw angle
1842
            A_rotation = self.RotationMatrix(yaw)
×
1843

1844
            # Rotate the turbine after shifting (x0, y0, z0) to the center of the turbine
1845
            xs0 = np.dot(coords - x0, A_rotation)
×
1846

1847
            for doublet in range(turbsPerPlatform):
×
1848

1849
                offset = np.zeros(self.problem.dom.dim)
×
1850
                offset[0] = xp_offset
×
1851
                offset[1] = yp_offset*(-1)**doublet
×
1852

1853
                # Offset each turbine from the center of rotation
1854
                xs = xs0 - offset
×
1855

1856
                # Normal to blades: Create the function that represents the Thickness of the turbine
1857
                T_norm = 1.902701539733748
×
1858
                T = np.exp(-(xs[:, 0]/W)**10.0)/(T_norm*W)
×
1859

1860
                # Tangential to blades: Create the function that represents the Disk of the turbine
1861
                D_norm = 2.884512175878827
×
1862
                if self.problem.dom.dim == 2:
×
1863
                    D1 = (xs[:, 1]/R)**2.0
×
1864
                else:
1865
                    D1 = (xs[:, 1]/R)**2.0 + (xs[:, 2]/R)**2.0
×
1866

1867
                D = np.exp(-D1**5.0)/(D_norm*R**2.0)
×
1868

1869
                # Create the function that represents the force
1870
                if self.problem.dom.dim == 2:
×
1871
                    r = xs[:, 1]
×
1872
                else:
1873
                    r = np.sqrt(xs[:, 1]**2.0 + xs[:, 2]**2.0)
×
1874

1875
                F = 4.0*0.5*(np.pi*R**2.0)*ma/(1.0 - ma)*(r/R*np.sin(np.pi*r/R) + 0.5) * 1.0/.81831
×
1876

1877
                u_vec = self.problem.u_k1.vector()[:]
×
1878
                ux = u_vec[0::self.problem.dom.dim]
×
1879
                uy = u_vec[1::self.problem.dom.dim]
×
1880
                uD = ux*np.cos(yaw) + uy*np.sin(yaw)
×
1881

1882
                tf_array[:, 0] = tf_array[:, 0] + F*T*D*np.cos(yaw)*uD**2.0
×
1883
                tf_array[:, 1] = tf_array[:, 1] + F*T*D*np.sin(yaw)*uD**2.0
×
1884

1885

1886
        # Riffle shuffle the array elements into a FEniCS-style vector
1887
        for k in range(self.problem.dom.dim):
×
1888
            tf_vec[k::self.problem.dom.dim] = tf_array[:, k]
×
1889

1890
        tf_vec[np.abs(tf_vec) < 1e-50] = 0.0
×
1891

1892
        # Set the vector elements
1893
        self.problem.tf.vector()[:] = tf_vec
×
1894

1895
    # ================================================================
1896

1897
    def RotationMatrix(self, yaw):
1✔
1898
        cosYaw = np.cos(yaw)
×
1899
        sinYaw = np.sin(yaw)
×
1900

1901
        if self.problem.dom.dim == 2:
×
1902
            A_rotation = np.array([[cosYaw, -sinYaw],
×
1903
                                   [sinYaw,  cosYaw]])
1904
        else:
1905
            A_rotation = np.array([[cosYaw, -sinYaw, 0.0],
×
1906
                                   [sinYaw,  cosYaw, 0.0],
1907
                                   [   0.0,     0.0, 1.0]])
1908

1909
        return A_rotation
×
1910

1911
    # ================================================================
1912

1913

1914
    def modifyInletVelocity(self, simTime, bcu):
1✔
1915

1916
        # Define tolerance
1917
        tol = 1e-6
×
1918

1919
        def left_wall(x, on_boundary):
×
1920
            return on_boundary and x[0] < self.problem.dom.x_range[0] + tol
×
1921

1922
        HH_vel = self.problem.bd.HH_vel
×
1923

1924
        # Get the coordinates using the vector funtion space, V
1925
        coords = self.problem.fs.V.tabulate_dof_coordinates()
×
1926
        coords = np.copy(coords[0::self.problem.dom.dim, :])
×
1927

1928
        # Create a function representing to the inlet velocity
1929
        vel_inlet_func = Function(self.problem.fs.V)
×
1930

1931
        inlet_type = 1
×
1932

1933
        if inlet_type == 1:
×
1934
            # Create arrays for the steady, vortex, and combined velocities
1935
            vel_steady = np.zeros(np.shape(coords))
×
1936
            vel_steady[:, 0] = HH_vel
×
1937
            # print(HH_vel)
1938

1939
            vel_vort = np.zeros(np.shape(coords))
×
1940
            vel_inlet = np.zeros(np.shape(coords))
×
1941

1942
            # Specify the vortex radius
1943
            vortRad = 1000
×
1944
            vortRad2 = vortRad**2
×
1945

1946
            # Specify the vortex velocity and calculate its position from the starting point
1947
            vortVel = 1.0
×
1948

1949
            period = 1000.0
×
1950
            xd = period/2 - vortVel*(simTime%period)
×
1951

1952
            fac = 0.1
×
1953
            sep = 650
×
1954
            Tau = 1000
×
1955

1956
            for k, x in enumerate(coords):
×
1957
                if x[0] < self.problem.dom.x_range[0] + tol:
×
1958

1959
                    # xd should replace x[0] in the following equations
1960
                    if np.abs(xd) < 1e-3:
×
1961
                        xd = 1e-3
×
1962

1963
                    cp = ((x[1] + sep/2)**2 + xd**2)/(4*Tau)
×
1964
                    cn = ((x[1] - sep/2)**2 + xd**2)/(4*Tau)
×
1965

1966
                    # U-velocity
1967
                    vel_inlet[k, 0] = fac*((1 - np.exp(-cp))/cp*(x[1] + sep/2) -\
×
1968
                                           (1 - np.exp(-cn))/cn*(x[1] - sep/2)) + 1
1969

1970
                    # V-velocity
1971
                    vel_inlet[k, 1] = fac*(-(1 - np.exp(-cp))/cp*xd +\
×
1972
                                            (1 - np.exp(-cn))/cn*xd)
1973

1974
                    norm = np.sqrt(vel_inlet[k, 0]*vel_inlet[k, 0] + vel_inlet[k, 1]*vel_inlet[k, 1])
×
1975

1976
                    if norm > 10.0:
×
1977
                        vel_inlet[k, 0] = vel_inlet[k, 0]/norm*10.0
×
1978
                        vel_inlet[k, 1] = vel_inlet[k, 1]/norm*10.0
×
1979

1980
                    # dx = x - vortPos
1981
                    # dist2 = dx[0]*dx[0] + dx[1]*dx[1]
1982

1983
                    # if dist2 < vortRad2:
1984
                    #     theta = np.arctan2(dx[1], dx[0])
1985
                    #     fac = 1.0 - np.sqrt(dist2/vortRad2)
1986
                    #     vel_vort[k, 0] = -np.sin(theta)
1987
                    #     vel_vort[k, 1] = np.cos(theta)
1988
                    # else:
1989
                    #     fac = 0.0
1990
                    #     vel_vort[k, 0] = 0.0
1991
                    #     vel_vort[k, 1] = 0.0
1992

1993
                    # vel_inlet[k, :] = (1.0-fac)*vel_steady[k, :] + HH_vel*fac*vel_vort[k, :]
1994

1995
        elif inlet_type == 2:
×
1996
            jet_rad = 400
×
1997

1998
            vel_inlet = np.zeros(np.shape(coords))
×
1999

2000
            for k, x in enumerate(coords):
×
2001
                if x[0] < self.problem.dom.x_range[0] + tol:
×
2002
                    if np.abs(x[1]) < jet_rad:
×
2003
                        thetaMax = 15.0/180.0*np.pi
×
2004

2005
                        theta = thetaMax*np.sin(simTime/1000*2*np.pi)
×
2006

2007
                        vel_inlet[k, 0] = 2.0*HH_vel*np.cos(theta)
×
2008
                        vel_inlet[k, 1] = 2.0*HH_vel*np.sin(theta)
×
2009
                    else:
2010
                        vel_inlet[k, 0] = HH_vel
×
2011
                        vel_inlet[k, 1] = 0.0
×
2012

2013

2014
        # Riffle shuffle the array elements into a 1D vector
2015
        vel_inlet_vector = np.zeros(np.size(vel_inlet))
×
2016

2017
        for k in range(self.problem.dom.dim):
×
2018
            vel_inlet_vector[k::self.problem.dom.dim] = vel_inlet[:, k]
×
2019

2020
        # Assign the function the vector of values
2021
        vel_inlet_func.vector()[:] = vel_inlet_vector
×
2022

2023

2024
        # Update the inlet velocity
2025
        bcu[0] = DirichletBC(self.problem.fs.V, vel_inlet_func, left_wall)
×
2026

2027
        return bcu
×
2028

2029

2030
# ================================================================
2031

2032
class MultiAngleSolver(SteadySolver):
1✔
2033
    """
2034
    This solver will solve the problem using the steady state solver for every
2035
    angle in angles.
2036

2037
    Args: 
2038
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
2039
        angles (list): A list of wind inflow directions.
2040
    """ 
2041

2042
    def __init__(self,problem):
1✔
2043
        super(MultiAngleSolver, self).__init__(problem)
×
2044
        if self.params["domain"]["type"] in ["imported"]:
×
2045
            raise ValueError("Cannot use a Multi-Angle Solver with an "+self.params["domain"]["type"]+" domain.")
×
2046
        self.orignal_solve = super(MultiAngleSolver, self).Solve
×
2047
        
2048
        if self.problem.dom.raw_inflow_angle is None:
×
NEW
2049
            self.wind_range = [0, 360,self.num_wind_angles]
×
2050
        elif isinstance(self.problem.dom.raw_inflow_angle,list):
×
2051
            if len(self.problem.dom.raw_inflow_angle)==3:
×
2052
                self.wind_range = self.problem.dom.raw_inflow_angle
×
2053
            else:
2054
                self.wind_range = [self.problem.dom.raw_inflow_angle[0],self.problem.dom.raw_inflow_angle[1],self.num_wind_angles]
×
2055
        else:
NEW
2056
            self.wind_range = [self.problem.dom.raw_inflow_angle,self.problem.dom.raw_inflow_angle+360,self.num_wind_angles]
×
2057

2058

2059
        self.angles = np.linspace(*self.wind_range,endpoint=self.endpoint)
×
2060
        self.angles = meteor_to_math(self.angles)
×
2061
        # self.angles += self.angle_offset
2062

2063
        # print(self.wind_range)
2064
        # print(self.angles)
2065
        # exit()
2066

2067
    def Solve(self):
1✔
2068
        for i, theta in enumerate(self.angles):
×
2069
            self.fprint("Performing Solve {:d} of {:d}".format(i+1,len(self.angles)),special="header")
×
2070
            self.fprint("Wind Angle: "+repr(math_to_meteor(theta)))
×
2071
            if i > 0 or not near(theta,self.problem.dom.inflow_angle):
×
2072
                self.problem.dom.inflow_angle = theta
×
2073
                self.ChangeWindAngle(theta)
×
2074
            self.iter_val = math_to_meteor(theta)
×
2075
            self.orignal_solve()
×
2076
            self.fprint("Finished Solve {:d} of {:d}".format(i+1,len(self.angles)),special="footer")
×
2077

2078
class TimeSeriesSolver(SteadySolver):
1✔
2079
    """
2080
    This solver will solve the problem using the steady state solver for every
2081
    angle in angles.
2082

2083
    Args: 
2084
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
2085
        angles (list): A list of wind inflow directions.
2086
    """ 
2087

2088
    def __init__(self,problem):
1✔
2089
        super(TimeSeriesSolver, self).__init__(problem)
×
2090
        if self.params["domain"]["type"] in ["imported"]:
×
2091
            raise ValueError("Cannot use a Multi-Angle Solver with an "+self.params["domain"]["type"]+" domain.")
×
2092
        self.orignal_solve = super(TimeSeriesSolver, self).Solve
×
2093

2094
        raw_data = np.loadtxt(self.velocity_path,comments="#")
×
2095
        self.times = raw_data[:,0]
×
2096
        self.speeds = raw_data[:,1]
×
2097
        self.angles = raw_data[:,2]
×
2098
        self.num_solve = len(self.speeds)
×
2099

2100
    def Solve(self):
1✔
2101
        for i in range(self.num_solve):
×
2102
            time  = self.times[i]
×
2103
            theta = self.angles[i]
×
2104
            speed = self.speeds[i]
×
2105
            self.fprint("Performing Solve {:d} of {:d}".format(i+1,len(self.angles)),special="header")
×
2106
            self.fprint("Time: "+repr(time))
×
2107
            self.fprint("Wind Angle: "+repr(theta))
×
2108
            self.fprint("Wind Speed: "+repr(speed))
×
2109
            if i > 0 or not near(speed,self.problem.bd.HH_vel):
×
2110
                self.ChangeWindSpeed(speed)
×
2111
            if i > 0 or not near(theta,self.problem.dom.inflow_angle):
×
2112
                self.ChangeWindAngle(theta)
×
2113
            self.iter_val = time
×
2114
            self.orignal_solve()
×
2115

2116
            self.fprint("Finished Solve {:d} of {:d}".format(i+1,len(self.angles)),special="footer")
×
2117

2118

2119

2120

2121

2122

2123

2124

2125

2126

2127

2128

2129

2130

2131

2132

2133

2134

2135

2136

2137

2138

2139

2140

2141

2142
##### OLD CODE FOR FUTURE REFERENCE ####
2143

2144

2145
    # # def CalculatePowerFunctional_n(self,inflow_angle = 0.0):
2146
    # def CalculatePowerFunctional(self,inflow_angle = 0.0):
2147

2148
    #     # if self.problem.dom.dim == 3:
2149
    #     if self.problem.dom.dim <= 3:
2150
    #         # print("here")
2151
    #         ### Reconstruct Turbine Force ###
2152
    #         tf = self.problem.tf1*self.u_next[0]**2+self.problem.tf2*self.u_next[1]**2+self.problem.tf3*self.u_next[0]*self.u_next[1]
2153

2154

2155
    #         W = self.problem.farm.thickness[0]*1.0
2156
    #         R = self.problem.farm.RD[0]/2.0 
2157

2158
    #         T_norm = 2.0*gamma(7.0/6.0)
2159
    #         D_norm = pi*gamma(4.0/3.0)
2160
    #         volNormalization_3D = T_norm*D_norm*W*R**2.0
2161
    #         print("3D Volume: "+repr(volNormalization_3D))
2162

2163
    #         T_norm = 2.0*gamma(7.0/6.0)
2164
    #         D_norm = 2.0*gamma(7.0/6.0)
2165
    #         volNormalization_2D = T_norm*D_norm*W*R
2166
    #         print("3D Volume: "+repr(volNormalization_2D))
2167

2168

2169
    #         if self.problem.dom.dim == 2:
2170
    #             dim_scale = volNormalization_3D/volNormalization_2D
2171
    #             print(dim_scale)
2172
    #             dim_scale = pi*R/2
2173
    #             print(dim_scale)
2174
    #             dim_scale = 2*R*R*volNormalization_2D/volNormalization_3D
2175
    #             print(dim_scale)
2176
    #         else: 
2177
    #             dim_scale = 1.0
2178

2179
    #         ### Calculate Power ###
2180
    #         J = dot(tf*dim_scale,self.u_next)*dx
2181

2182
    #         ### Save Individual Powers ###
2183
    #         if self.save_power:
2184
    #             J_list=np.zeros(self.problem.farm.numturbs+1)
2185
    #             J_list[-1]=assemble(J,**self.extra_kwarg)
2186

2187
    #             if self.problem.farm.actuator_disks_list is not None:
2188
    #                 for i in range(self.problem.farm.numturbs):
2189
    #                     yaw = self.problem.farm.myaw[i]+inflow_angle
2190
    #                     tf1 = self.problem.farm.actuator_disks_list[i] * cos(yaw)**2
2191
    #                     tf2 = self.problem.farm.actuator_disks_list[i] * sin(yaw)**2
2192
    #                     tf3 = self.problem.farm.actuator_disks_list[i] * 2.0 * cos(yaw) * sin(yaw)
2193
    #                     tf = tf1*self.u_next[0]**2+tf2*self.u_next[1]**2+tf3*self.u_next[0]*self.u_next[1]
2194
    #                     J_list[i] = assemble(dot(tf*dim_scale,self.u_next)*dx,**self.extra_kwarg)
2195

2196
    #             for j in J_list:
2197
    #                 print(j)
2198
    #             # exit()
2199
    #     # else:
2200
    #     #     J=0
2201
    #     #     J_list=np.zeros(self.problem.farm.numturbs+1)
2202
    #     #     for i in range(self.problem.farm.numturbs):
2203

2204
    #     #         ### Set some Values ###
2205
    #     #         yaw = self.problem.farm.myaw[i]+inflow_angle
2206
    #     #         W = self.problem.farm.W[i]
2207
    #     #         R = self.problem.farm.RD[i]/2.0 
2208
    #     #         A = pi*R**2.0
2209
    #     #         a = self.problem.farm.ma[i]
2210
    #     #         C_tprime = 4*a/(1-a)
2211
    #     #         C_pprime = 0.45/(1-a)**3
2212

2213
    #     #         ### Define Integral Constants ###
2214
    #     #         T_norm = 2.0*gamma(7.0/6.0)
2215
    #     #         D_norm = 2.0*gamma(7.0/6.0)
2216
    #     #         h1 = float(hyper([],[1/3, 2/3, 5/6, 7/6],-(np.pi**6/46656)))
2217
    #     #         h2 = float(hyper([],[2/3, 7/6, 4/3, 3/2],-(np.pi**6/46656)))
2218
    #     #         h3 = float(hyper([],[4/3, 3/2, 5/3, 11/6],-(np.pi**6/46656)))
2219
    #     #         SD_norm = (1/3*np.pi**(3/2)*h1 - 1/15*np.pi**3*gamma(11/6)*h2 + gamma(7/6)*(1 + 1/360*np.pi**5*h3))/(.81831)
2220

2221
    #     #         ### Reconstruct Turbine Force ###
2222
    #     #         tf1 = self.problem.farm.actuator_disks_list[i] * cos(yaw)**2
2223
    #     #         tf2 = self.problem.farm.actuator_disks_list[i] * sin(yaw)**2
2224
    #     #         tf3 = self.problem.farm.actuator_disks_list[i] * 2.0 * cos(yaw) * sin(yaw)
2225
    #     #         tf = tf1*self.u_next[0]**2+tf2*self.u_next[1]**2+tf3*self.u_next[0]*self.u_next[1]
2226
    #     #         tf = tf*T_norm*D_norm*W*R
2227

2228
    #     #         ### Calculate Volume of the Domain ###
2229
    #     #         unit = Function(self.problem.fs.Q)
2230
    #     #         unit.vector()[:] = 1.0
2231
    #     #         vol = assemble(unit*dx)
2232

2233
    #     #         ### Calculate Integral of Actuator Disk ###
2234
    #     #         if self.problem.farm.force == "constant":
2235
    #     #             Va = C_tprime*T_norm*D_norm*W*R*R
2236
    #     #         elif self.problem.farm.force == "sine":
2237
    #     #             Va = C_tprime*T_norm*SD_norm*W*R*R
2238

2239
    #     #         ### Calculate Power ###
2240
    #     #         # J_current = 0.5*A*C_pprime*(assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2241
    #     #         # J_current = 0.5*A*C_pprime/vol*(dot(tf,self.u_next)/Va)**3*dx
2242
    #     #         J_current = 0.5*A*C_pprime/vol*(dot(tf,self.u_next)/Va)*dx
2243
    #     #         # J_current = 0.5*A*C_pprime/vol*(unit/Va)**3*dx
2244
    #     #         # J_current = unit*0.5*A*C_pprime/vol*(1/Va)**3*dx
2245
    #     #         # J_current = unit*Va*dx
2246
    #     #         J += J_current
2247

2248
    #     #         if self.save_power:
2249
    #     #             J_list[i] = assemble(J_current)
2250

2251
    #     #     if self.save_power:
2252
    #     #         J_list[-1]=np.sum(J_list[:-1])
2253
    #     #         print()
2254
    #     #         for j in J_list:
2255
    #     #             print(j)
2256
    #     #         # exit()
2257

2258
    #         folder_string = self.params.folder+"/data/"
2259
    #         if not os.path.exists(folder_string): os.makedirs(folder_string)
2260

2261
    #         if self.J_saved:
2262
    #             f = open(folder_string+"power_data.txt",'ab')
2263
    #         else:
2264
    #             f = open(folder_string+"power_data.txt",'wb')
2265
    #             self.J_saved = True
2266

2267
    #         np.savetxt(f,[J_list])
2268
    #         f.close()
2269

2270
    #     return J
2271
        
2272
    # # def CalculatePowerFunctional_o(self,delta_yaw = 0.0):
2273
    # # # def CalculatePowerFunctional(self,delta_yaw = 0.0):
2274
    # #     self.fprint("Computing Power Functional")
2275

2276
    # #     x=SpatialCoordinate(self.problem.dom.mesh)
2277
    # #     J=0.
2278
    # #     J_list=np.zeros(self.problem.farm.numturbs+1)
2279
    # #     for i in range(self.problem.farm.numturbs):
2280

2281
    # #         mx = self.problem.farm.mx[i]
2282
    # #         my = self.problem.farm.my[i]
2283
    # #         mz = self.problem.farm.mz[i]
2284
    # #         x0 = [mx,my,mz]
2285
    # #         W = self.problem.farm.thickness[i]*1.0
2286
    # #         R = self.problem.farm.RD[i]/2.0 
2287
    # #         ma = self.problem.farm.ma[i]
2288
    # #         yaw = self.problem.farm.myaw[i]+delta_yaw
2289
    # #         u = self.u_next
2290
    # #         A = pi*R**2.0
2291
    # #         C_tprime = 4*ma/(1-ma)
2292
    # #         C_pprime = 0.45/(1-ma)**3
2293
            
2294
    # #         ### Rotate and Shift the Turbine ###
2295
    # #         xs = self.problem.farm.YawTurbine(x,x0,yaw)
2296
    # #         u_d = u[0]*cos(yaw) + u[1]*sin(yaw)
2297

2298
    # #         ### Create the function that represents the Thickness of the turbine ###
2299
    # #         T = exp(-pow((xs[0]/W),6.0))
2300

2301
    # #         if self.problem.dom.dim == 3:
2302
    # #             # WTGbase = Expression(("cos(yaw)","sin(yaw)","0.0"),yaw=yaw,degree=1)
2303
    # #             WTGbase = as_vector((cos(yaw),sin(yaw),0.0))
2304

2305
    # #             ### Create the function that represents the Disk of the turbine
2306
    # #             r = sqrt(xs[1]**2.0+xs[2]**2.0)/R
2307
    # #             D = exp(-pow(r,6.0))
2308
    # #             # D = exp(-pow((pow((xs[1]/R),2)+pow((xs[2]/R),2)),6.0))
2309

2310
    # #             volNormalization = assemble(T*D*dx)
2311
    # #             print(volNormalization)
2312
    # #             T_norm = 2.0*gamma(7.0/6.0)
2313
    # #             D_norm = pi*gamma(4.0/3.0)
2314
    # #             volNormalization = T_norm*D_norm*W*R**2.0
2315
    # #             print(volNormalization)
2316

2317
    # #             ### Create the function that represents the force ###
2318
    # #             if self.problem.farm.force == "constant":
2319
    # #                 F = 0.5*A*C_tprime
2320
    # #             elif self.problem.farm.force == "sine":
2321
    # #                 # r = sqrt(xs[1]**2.0+xs[2]**2)
2322
    # #                 F = 0.5*A*C_tprime*(r*sin(pi*r)+0.5)/(.81831)
2323

2324
    # #             J_current = dot(F*T*D*WTGbase*u_d**2,u)*dx
2325
    # #             J += J_current
2326
    # #             if self.save_power:
2327
    # #                 J_list[i] = assemble(J_current)
2328

2329
    # #         else:
2330
    # #             # WTGbase = Expression(("cos(yaw)","sin(yaw)"),yaw=yaw,degree=1)
2331
    # #             WTGbase = as_vector((cos(yaw),sin(yaw)))
2332

2333
    # #             ### Create the function that represents the Disk of the turbine
2334
    # #             r = sqrt(xs[1]**2.0+xs[2]**2.0)/R
2335
    # #             D = exp(-pow(r,6.0))
2336

2337

2338
    # #             # T_norm = 2.0*gamma(7.0/6.0)
2339
    # #             # D_norm = 2.0*gamma(7.0/6.0)
2340
    # #             # h1 = float(hyper([],[1/3, 2/3, 5/6, 7/6],-(np.pi**6/46656)))
2341
    # #             # h2 = float(hyper([],[2/3, 7/6, 4/3, 3/2],-(np.pi**6/46656)))
2342
    # #             # h3 = float(hyper([],[4/3, 3/2, 5/3, 11/6],-(np.pi**6/46656)))
2343
    # #             # SD_norm = (1/3*np.pi**(3/2)*h1 - 1/15*np.pi**3*gamma(11/6)*h2 + gamma(7/6)*(1 + 1/360*np.pi**5*h3))/(.81831)
2344
    # #             # volNormalization = T_norm*SD_norm*W*R**(self.problem.dom.dim)
2345
    # #             volNormalization = assemble(T*D*dx)
2346

2347
    # #             T_norm = 2.0*gamma(7.0/6.0)
2348
    # #             D_norm = pi*gamma(4.0/3.0)
2349
    # #             volNormalization_3D = T_norm*D_norm*W*R**2.0
2350
    # #             # print(volNormalization_3D)
2351

2352

2353
    # #             T_norm = 2.0*gamma(7.0/6.0)
2354
    # #             D_norm = 2.0*gamma(7.0/6.0)
2355
    # #             volNormalization_2D = T_norm*D_norm*W*R
2356
    # #             # print(volNormalization_2D)
2357

2358

2359
    # #             print(volNormalization,volNormalization_2D,volNormalization_3D,2*volNormalization_2D/volNormalization_3D)
2360

2361
    # #             ### Create the function that represents the force ###
2362
    # #             if self.problem.farm.force == "constant":
2363
    # #                 F = 0.5*self.problem.farm.RD[i]*C_tprime    
2364
    # #             elif self.problem.farm.force == "sine":
2365
    # #                 F = 0.5*self.problem.farm.RD[i]*C_tprime*(r*sin(pi*r)+0.5)/(.81831)
2366

2367
    # #             # V  = assemble(F*T*D*dx)
2368
    # #             # Va = float(C_tprime)*T_norm*SD_norm*W*R*R
2369
    # #             # print(V,Va,V/Va,V/(W*R*R),Va/(W*R*R))
2370

2371
    # #             J_current = assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)
2372
    # #             # J_current = (assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)
2373
    # #             # J_current = 0.5*A*C_pprime*(assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)/assemble(F*T*D*dx))
2374
    # #             # print(float(J_current))
2375
    # #             # J_current_old = 0.5*A*C_pprime*(assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2376
    # #             # J_current = 0.5*A*C_pprime*(assemble(T*D*u*dx)/assemble(T*D*dx))**3
2377

2378
    # #             # J_3D = [1692363.167076575,801778.7751333286,545135.3528982735]
2379
    # #             # J_3D = [767698.3159772983,420644.4831798226,291267.477222329]
2380
    # #             # print(float(J_current_old),float(J_current_old/J_current),float(J_3D[i]/J_current))
2381
    # #             # J_current = (assemble(0.5*A*C_pprime*F*T*D*u_d*dx)/assemble(F*T*D*dx))
2382

2383

2384

2385

2386
    # #             ### LIST O PROBLEMS ###
2387
    # #             # 1. Unfortunately, this is not a form so cannot be used with dolfin_adjoint (this just returns a float)
2388
    # #             # 2. Where did WTGbase go? I know it need to be a scaler but does yaw angle not matter in 2D?
2389
    # #             # 3. Should the C_pprime term be inside the numerator? 
2390
    # #             # 4. Are you positive you want to divide by the total force (F*T*D) instead of just the space kernal (T*D)
2391

2392

2393

2394
 
2395

2396

2397

2398

2399

2400

2401

2402

2403

2404

2405
    # #             # J_current = 0.5*A*C_pprime*(1/assemble(F*T*D*dx))**3
2406
    # #             J += J_current
2407
    # #             if self.save_power:
2408
    # #                 # J_list[i] = (assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2409
    # #                 J_list[i] = J_current
2410

2411
    # #     if self.save_power:
2412
    # #         J_list[-1]=np.sum(J_list[:-1])
2413
    # #         print()
2414
    # #         for j in J_list:
2415
    # #             print(j)
2416
    # #         exit()
2417

2418
    # #         folder_string = self.params.folder+"/data/"
2419
    # #         if not os.path.exists(folder_string): os.makedirs(folder_string)
2420

2421
    # #         if self.J_saved:
2422
    # #             f = open(folder_string+"power_data.txt",'ab')
2423
    # #         else:
2424
    # #             f = open(folder_string+"power_data.txt",'wb')
2425
    # #             self.J_saved = True
2426

2427
    # #         np.savetxt(f,[J_list])
2428
    # #         f.close()
2429

2430
    # #     return J
2431

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