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

NREL / WindSE / 9522960252

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

Pull #115

github

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

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

1 existing line in 1 file now uncovered.

5155 of 7713 relevant lines covered (66.84%)

0.67 hits per line

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

50.32
/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)
1✔
172
            self.params.Save(p,"pressure",subfolder="solutions/",val=val,file=self.p_file)
1✔
173
            self.params.Save(self.nu_T,"eddy_viscosity",subfolder="solutions/",val=val,file=self.nuT_file)
1✔
174
            if self.problem.dom.dim == 3:
1✔
175
                self.params.Save(self.ReyStress,"Reynolds_stresses",subfolder="solutions/",val=val,file=self.ReyStress_file)
1✔
176
                self.params.Save(self.vertKE,"Vertical KE",subfolder="solutions/",val=val,file=self.vertKE_file)
1✔
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)
1✔
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
    @no_annotations
1✔
235
    def EvaulateThrustFunctional(self):
1✔
NEW
236
        kwargs = {
×
237
            "iter_val": self.iter_val,
238
            "simTime": self.simTime
239
        }
NEW
240
        out = self.problem.farm.save_thrust(self.problem.u_k,self.problem.dom.inflow_angle, **kwargs)
×
NEW
241
        return out
×
242

243

244
    def EvaluateObjective(self,output_name="objective_data",opt_iter=-1):
1✔
245
        self.fprint("Evaluating Objective Data",special="header")
1✔
246
        start = time.time()
1✔
247

248
        first_call = True
1✔
249
        if self.J_saved:
1✔
250
            first_call = False
1✔
251

252
        annotate = self.params.dolfin_adjoint
1✔
253

254
        ### Iterate over objectives ###
255
        obj_list = [opt_iter, self.iter_val, self.simTime]
1✔
256
        for key, obj_kwargs in self.objective_type.items():
1✔
257
            if isinstance(key, int):
1✔
258
                objective_name = obj_kwargs["type"]
1✔
259
            else:
260
                objective_name = key
1✔
261
            objective_func = obj_funcs.objective_functions[objective_name]
1✔
262
            args = (self, (self.problem.dom.inflow_angle))
1✔
263
            kwargs = {"first_call": first_call, "annotate": annotate}
1✔
264
            kwargs.update(obj_kwargs)
1✔
265
            out = obj_funcs._annotated_objective(objective_func, *args, **kwargs)
1✔
266
            obj_list.append(out)
1✔
267
        J = obj_list[3] #grab first objective
1✔
268

269
        # ### Flip the sign because the objective is minimized but these values are maximized
270
        # for i in range(1,len(obj_list)):
271
        #     obj_list[i] = -obj_list[i]
272

273
        ### Save to csv ###
274
        if self.J_saved:
1✔
275
            self.params.save_csv(output_name,data=[obj_list],subfolder=self.params.folder+"data/",mode='a')
1✔
276
        else:
277
            ### Generate the header ###
278
            header = "Opt_iter, Iter_Val, Time, "
1✔
279
            for key, val in self.objective_type.items():
1✔
280
                if isinstance(key, int):
1✔
281
                    name = val["type"]
1✔
282
                else:
283
                    name = key
1✔
284
                header += name + ", "
1✔
285
            header = header[:-2]
1✔
286

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

290
        stop = time.time()
1✔
291
        self.fprint("Complete: {:1.2f} s".format(stop-start),special="footer")
1✔
292
        return J
1✔
293

294
    def marker(self, u, simTime, adj_tape_file):
1✔
295
        return u
×
296

297
    def control_updater(self, J, problem, time=None):
1✔
298
        return J
1✔
299

300

301

302

303

304
class SteadySolver(GenericSolver):
1✔
305
    """
306
    This solver is for solving the steady state problem
307

308
    Args:
309
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
310
    """
311
    def __init__(self,problem):
1✔
312
        super(SteadySolver, self).__init__(problem)
1✔
313
        self.u_k,self.p_k = split(self.problem.up_k)
1✔
314

315
    def Solve(self):
1✔
316
        """
317
        This solves the problem setup by the problem object.
318
        """
319

320
        ### Save Files before solve ###
321
        self.fprint("Saving Input Data",special="header")
1✔
322
        if "mesh" in self.params.output:
1✔
323
            self.problem.dom.Save(val=self.iter_val)
1✔
324
        if "initial_guess" in self.params.output:
1✔
325
            self.problem.bd.SaveInitialGuess(val=self.iter_val)
1✔
326
        if "height" in self.params.output and self.problem.dom.dim == 3:
1✔
327
            self.problem.bd.SaveHeight(val=self.iter_val)
1✔
328
        if "turbine_force" in self.params.output:
1✔
329
            self.problem.farm.save_functions(val=self.iter_val)
1✔
330
        self.fprint("Finished",special="footer")
1✔
331

332
        # exit()
333

334
        ####################################################################
335
        ### This is the better way to define a nonlinear problem but it
336
        ### doesn't play nice with dolfin_adjoint
337
        ### Define Jacobian ###
338
        # dU = TrialFunction(self.problem.fs.W)
339
        # J  = derivative(self.problem.F,  self.problem.up_k, dU)
340

341
        # ### Setup nonlinear solver ###
342
        # nonlinear_problem = NonlinearVariationalProblem(self.problem.F, self.problem.up_k, self.problem.bd.bcs, J)
343
        # nonlinear_solver  = NonlinearVariationalSolver(nonlinear_problem)
344

345
        # ### Set some parameters ###
346
        # solver_parameters = nonlinear_solver.parameters['newton_solver']['linear_solver'] = 'gmres'
347

348
        # # nonlinear_solver.ksp().setGMRESRestart(40)
349

350
        # def print_nested_dict(d, indent):
351
        #     for key, value in d.items():
352
        #         if hasattr(value, 'items'):
353
        #             for i in range(indent):
354
        #                 print('\t', end = '')
355
        #             print(key, ":")
356
        #             indent += 1
357
        #             print_nested_dict(value, indent)
358
        #             indent -= 1
359
        #         else:
360
        #             for i in range(indent):
361
        #                 print('\t', end = '')
362
        #             print(key, ":", value)
363

364
        # nonlinear_solver.parameters
365
        # print_nested_dict(nonlinear_solver.parameters, 0)
366
        # exit()
367

368

369

370
        # print(type(solver_parameters))
371

372
        # info(solver_parameters)
373
        # solver_parameters["nonlinear_solver"] = "snes"
374
        # solver_parameters["snes_solver"]["linear_solver"] = "mumps"
375
        # solver_parameters["snes_solver"]["maximum_iterations"] = 50
376
        # solver_parameters["snes_solver"]["error_on_nonconvergence"] = False
377
        # solver_parameters["snes_solver"]["line_search"] = "bt" # Available: basic, bt, cp, l2, nleqerr
378

379
        ### Solve the problem ###
380
        # self.fprint("Solving",special="header")
381
        # start = time.time()
382
        # iters, converged = nonlinear_solver.solve()
383
        # stop = time.time()
384
        # self.fprint("Total Nonlinear Iterations: {:d}".format(iters))
385
        # self.fprint("Converged Successfully: {0}".format(converged))
386
        ####################################################################
387

388
        self.fprint("Solving with {0}".format(self.nonlinear_solver))
1✔
389
        if self.nonlinear_solver == "newton":
1✔
390
            self.fprint("Relaxation parameter = {: 1.2f}".format(self.newton_relaxation))
1✔
391

392
            krylov_options = {"absolute_tolerance": 9e-3,
1✔
393
                              "relative_tolerance": 9e-1,
394
                              "maximum_iterations": 5000}
395

396
            newton_options = {"relaxation_parameter": self.newton_relaxation,
1✔
397
                              "maximum_iterations": 150,
398
                              "linear_solver": "mumps",
399
                              "preconditioner": "default",
400
                              "absolute_tolerance": 1e-6,
401
                              "relative_tolerance": 1e-5,
402
                              "error_on_nonconvergence": False,
403
                              "krylov_solver": krylov_options}
404

405
            solver_parameters = {"nonlinear_solver": "newton",
1✔
406
                                 "newton_solver": newton_options}
407

408
        elif self.nonlinear_solver == "snes":
1✔
409
            # ### Add some helper functions to solver options ###
410

411
            krylov_options = {"absolute_tolerance":  1e-12,
1✔
412
                              "relative_tolerance":  1e-6,
413
                              "maximum_iterations":  5000,
414
                              "monitor_convergence": True}
415

416
            solver_parameters = {"nonlinear_solver": "snes",
1✔
417
                                 "snes_solver": {
418
                                 "absolute_tolerance": 1e-6,
419
                                 "relative_tolerance": 1e-5,
420
                                 "linear_solver": "mumps",
421
                                 "preconditioner": "default",
422
                                 "maximum_iterations": 150,
423
                                 "error_on_nonconvergence": False,
424
                                 "line_search": "bt", #[basic,bt,cp,l2,nleqerr]
425
                                 "krylov_solver": krylov_options
426
                                 }}
427

428
        else:
429
            raise ValueError("Unknown nonlinear solver type: {0}".format(self.nonlinear_solver))
×
430

431
        ### Start the Solve Process ###
432
        self.fprint("Solving",special="header")
1✔
433
        start = time.time()
1✔
434

435
        # ### Solve the Baseline Problem ###
436
        # solve(self.problem.F_sans_tf == 0, self.problem.up_k, self.problem.bd.bcs, solver_parameters=solver_parameters, **self.extra_kwarg)
437

438
        # ### Store the Baseline and Assign for the real solve ###
439
        # self.up_baseline = self.problem.up_k.copy(deepcopy=True)
440
        # self.problem.up_k.assign(self.up_baseline)
441

442
        ### Solve the real problem ###
443
        # mem0=memory_usage()[0]
444
        # 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)
445
        solve(self.problem.F == 0, self.problem.up_k, self.problem.bd.bcs, solver_parameters=solver_parameters)
1✔
446
        stop = time.time()
1✔
447

448
        self.fprint("Solve Complete: {:1.2f} s".format(stop-start),special="footer")
1✔
449
        # self.fprint("Memory Used:  {:1.2f} MB".format(mem_out-mem0))
450
        # self.u_k,self.p_k = self.problem.up_k.split(True)
451
        self.problem.u_k,self.problem.p_k = self.problem.up_k.split()
1✔
452

453
        # try:
454
        #     print(f"u(0, 0,150):   {self.problem.u_k([0.0, 0.0,150.0])}")
455
        # except:
456
        #     pass
457
        # try:
458
        #     print(f"u(0, 0,210):   {self.problem.u_k([0.0, 0.0,210.0])}")
459
        # except:
460
        #     pass
461
        # try:
462
        #     print(f"u(0,60,150):   {self.problem.u_k([0.0,60.0,150.0])}")
463
        # except:
464
        #     pass
465
        # print(f"max(u):        {self.problem.u_k.vector().max()}")
466
        # print(f"min(u):        {self.problem.u_k.vector().min()}")
467
        # print(f"integral(u_x): {assemble(self.problem.u_k[0]*dx)}")
468

469

470
        ### Hack into doflin adjoint to update the local controls at the start of the adjoint solve ###
471
        self.nu_T = project(self.problem.nu_T,self.problem.fs.Q,solver_type='gmres',preconditioner_type="hypre_amg",**self.extra_kwarg)
1✔
472
        if self.problem.dom.dim == 3:
1✔
473
            self.fprint("")
1✔
474
            self.fprint("Projecting Reynolds Stress")
1✔
475
            self.ReyStress = project(self.problem.ReyStress,self.problem.fs.T,solver_type='gmres',preconditioner_type="hypre_amg",**self.extra_kwarg)
1✔
476
            self.vertKE = project(self.problem.vertKE,self.problem.fs.Q,solver_type='gmres',preconditioner_type="hypre_amg",**self.extra_kwarg)
1✔
477

478
        # self.nu_T = None
479

480
        ### Save solutions ###
481
        if "solution" in self.params.output:
1✔
482
            self.fprint("Saving Solution",special="header")
1✔
483
            self.Save(val=self.iter_val)
1✔
484
            self.fprint("Finished",special="footer")
1✔
485

486
        ### calculate the power for each turbine ###
487
        ###################################
488
        ### Fix how angle is transfered ###
489
        ###################################
490
        # if self.save_power:
491
        #     self.SavePower(((self.iter_theta-self.problem.dom.inflow_angle)))
492

493

494
        ### Evaluate the objectives ###
495
        if self.optimizing or self.save_objective:
1✔
496
            self.J += self.EvaluateObjective()
1✔
497
            # self.J += self.objective_func(self,(self.iter_theta-self.problem.dom.inflow_angle))
498
            self.J = self.control_updater(self.J, self.problem)
1✔
499

500
        if self.save_power:
1✔
501
            self.EvaulatePowerFunctional()
1✔
502

503
        if self.save_thrust:
1✔
NEW
504
            self.EvaulateThrustFunctional()
×
505

506
            # print(self.outflow_markers)
507
            # self.J += -dot(self.problem.farm.rotor_disks,self.u_k)*dx
508

509
        # self.fprint("Speed Percent of Inflow Speed")
510
        # ps = []
511
        # for i in range(6):
512
        #     HH = self.problem.farm.HH[0]
513
        #     RD = self.problem.farm.RD[0]
514
        #     x_val = (i+1)*RD
515
        #     vel = self.problem.up_k([x_val,0,HH])
516
        #     vel = vel[0:3]
517
        #     nom = np.linalg.norm(vel)
518
        #     perc = nom/self.problem.bd.HH_vel
519
        #     ps.append(perc)
520
        #     self.fprint("Speed Percent at ("+repr(int(x_val))+", 0, "+repr(HH)+"): "+repr(perc))
521
        # print(ps)
522

523
        self.DebugOutput()
1✔
524

525
#
526
# ================================================================
527

528
class IterativeSteadySolver(GenericSolver):
1✔
529
    """
530
    This solver is for solving the iterative steady state problem
531

532
    Args:
533
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
534
    """
535
    def __init__(self,problem):
1✔
536
        super(IterativeSteadySolver, self).__init__(problem)
×
537
        self.u_k,self.p_k = split(self.problem.up_k)
×
538

539
    def Solve(self):
1✔
540
        """
541
        This solves the problem setup by the problem object.
542
        """
543

544
        def set_solver_mode(solver_type):
×
545
            if solver_type == 'steady':
×
546
                self.problem.dt_1.assign(0)
×
547
                self.problem.dt_2.assign(1)
×
548
                self.problem.dt_3.assign(1)
×
549

550
                sor_vel = 0.1
×
551
                sor_pr = 0.2
×
552

553
            elif solver_type == 'unsteady':
×
554
                delta_t = 100.0
×
555

556
                self.problem.dt_1.assign(1.0/delta_t)
×
557
                self.problem.dt_2.assign(1.0/delta_t)
×
558
                self.problem.dt_3.assign(delta_t)
×
559

560
                sor_vel = 1.0
×
561
                sor_pr = 1.0
×
562

563
            else:
564
                raise ValueError('Solver type should be "steady" or "unsteady".')
×
565

566
            return sor_vel, sor_pr
×
567

568
        def get_relaxation_param(a_min, a_max, a_c, a_cw, x):
×
569

570
            # a_min = minimum sor value
571
            # a_max = maximum sor value
572
            # a_c = crossover location
573
            # a_cw = crossover width
574

575
            alpha_exp = (1/a_cw)*(a_c - x)
×
576
            alpha = 1.0/(1.0+np.exp(alpha_exp))
×
577
            alpha = alpha*(a_max-a_min) + a_min
×
578
            alpha = float(alpha)
×
579

580
            return alpha
×
581

582
        ### Save Files before solve ###
583
        self.fprint("Saving Input Data",special="header")
×
584
        if "mesh" in self.params.output:
×
585
            self.problem.dom.Save(val=self.iter_val)
×
586
        if "initial_guess" in self.params.output:
×
587
            self.problem.bd.SaveInitialGuess(val=self.iter_val)
×
588
        if "height" in self.params.output and self.problem.dom.dim == 3:
×
589
            self.problem.bd.SaveHeight(val=self.iter_val)
×
590
        if "turbine_force" in self.params.output:
×
591
            self.problem.farm.SaveActuatorDisks(val=self.iter_val)
×
592
        self.fprint("Finished",special="footer")
×
593

594
        self.SaveTimeSeries(0)
×
595

596
        # Assemble left-hand side matrices
597
        A1 = assemble(self.problem.F1_lhs)
×
598
        A2 = assemble(self.problem.F2_lhs)
×
599
        A3 = assemble(self.problem.F3_lhs)
×
600
        A4 = assemble(self.problem.F4_lhs)
×
601
        A5 = assemble(self.problem.F5_lhs)
×
602

603
        # Apply boundary conditions to matrices
604
        [bc.apply(A1) for bc in self.problem.bd.bcu]
×
605
        [bc.apply(A2) for bc in self.problem.bd.bcp]
×
606
        [bc.apply(A3) for bc in self.problem.bd.bcu]
×
607
        [bc.apply(A4) for bc in self.problem.bd.bcp]
×
608

609
        # Assemble right-hand side vectors
610
        b1 = assemble(self.problem.F1_rhs)
×
611
        b2 = assemble(self.problem.F2_rhs)
×
612
        b3 = assemble(self.problem.F3_rhs)
×
613
        b4 = assemble(self.problem.F4_rhs)
×
614
        b5 = assemble(self.problem.F5_rhs)
×
615

616
        # Apply bounday conditions to vectors
617
        [bc.apply(b1) for bc in self.problem.bd.bcu]
×
618
        [bc.apply(b2) for bc in self.problem.bd.bcp]
×
619
        [bc.apply(b3) for bc in self.problem.bd.bcu]
×
620
        [bc.apply(b4) for bc in self.problem.bd.bcp]
×
621

622
        # Create solvers using the set_operator method
623
        # which ensures preconditioners are reused when possible
624
        # solver_1 = PETScKrylovSolver('gmres', 'jacobi')
625

626
        vel_sol_options = ['gmres', 'none']
×
627
        pr_sol_options = ['bicgstab', 'none']
×
628

629
        solver_1 = PETScKrylovSolver(vel_sol_options[0], vel_sol_options[1])
×
630
        solver_1.set_operator(A1)
×
631

632
        solver_2 = PETScKrylovSolver(pr_sol_options[0], pr_sol_options[1])
×
633
        solver_2.set_operator(A2)
×
634

635
        solver_3 = PETScKrylovSolver(vel_sol_options[0], vel_sol_options[1])
×
636
        solver_3.set_operator(A3)
×
637

638
        solver_4 = PETScKrylovSolver(pr_sol_options[0], pr_sol_options[1])
×
639
        solver_4.set_operator(A4)
×
640

641
        solver_5 = PETScKrylovSolver('cg', 'jacobi')
×
642
        solver_5.set_operator(A5)
×
643

644

645

646
        outer_tic = time.time()
×
647

648

649
        solver_type = 'steady'
×
650
        sor_vel, sor_pr = set_solver_mode(solver_type)
×
651

652
        seed_velocity = False
×
653
        seed_pressure = False
×
654

655

656
        if seed_velocity:
×
657
            self.problem.u_k.assign(self.problem.bd.bc_velocity)
×
658
            self.problem.u_k_old.assign(self.problem.bd.bc_velocity)
×
659

660
        if seed_pressure:
×
661
            if self.params.rank == 0:
×
662
                print('Calculating initial pressure from velocity guess.')
×
663

664
            A1 = assemble(self.problem.F1_lhs, tensor=A1)
×
665
            [bc.apply(A1) for bc in self.problem.bd.bcu]
×
666
            solver_1.set_operator(A1)
×
667

668
            # Step 1: Tentative velocity step
669
            b1 = assemble(self.problem.F1_rhs, tensor=b1)
×
670
            [bc.apply(b1) for bc in self.problem.bd.bcu]
×
671
            solver_1.solve(self.problem.u_hat.vector(), b1)
×
672

673
            # Step 2: Pressure correction step
674
            b2 = assemble(self.problem.F2_rhs, tensor=b2)
×
675
            [bc.apply(b2) for bc in self.problem.bd.bcp]
×
676

677
            solver_2.solve(self.problem.p_k.vector(), b2)
×
678

679

680

681
        max_iter = 10
×
682

683
        for iter_num in range(max_iter):
×
684
            tic = time.time()
×
685

686
            # sor_vel = get_relaxation_param(1e-3, 0.5, 100, 10, iter_num)
687
            sor_vel = get_relaxation_param(1e-1, 0.5, 20, 5, iter_num)
×
688
            # sor_vel = 0.1
689
            sor_pr = 1.6*sor_vel
×
690

691
            use_simpler = False
×
692

693
            # if iter_num > 0.9*max_iter and solver_type == 'unsteady':
694
            #     solver_type = 'steady'
695
                # dt_1, dt_2, dt_3, sor_vel, sor_pr = set_solver_mode(solver_type)
696

697
            if use_simpler:
×
698
                A1 = assemble(self.problem.F1_lhs, tensor=A1)
×
699
                [bc.apply(A1) for bc in self.problem.bd.bcu]
×
700
                solver_1.set_operator(A1)
×
701

702
                # Step 1: Tentative velocity step
703
                b1 = assemble(self.problem.F1_rhs, tensor=b1)
×
704
                [bc.apply(b1) for bc in self.problem.bd.bcu]
×
705
                solver_1.solve(self.problem.u_hat.vector(), b1)
×
706

707
                # u_hat.assign((1.0-sor_vel)*u_k + sor_vel*u_hat)
708

709
                # Step 2: Pressure correction step
710
                b2 = assemble(self.problem.F2_rhs, tensor=b2)
×
711
                [bc.apply(b2) for bc in self.problem.bd.bcp]
×
712

713
                solver_2.solve(self.problem.p_k.vector(), b2)
×
714

715

716
            # Re-solve for velocity
717
            A3 = assemble(self.problem.F3_lhs, tensor=A3)
×
718
            [bc.apply(A3) for bc in self.problem.bd.bcu]
×
719
            solver_3.set_operator(A3)
×
720

721
            # Step 1: Tentative velocity step
722
            b3 = assemble(self.problem.F3_rhs, tensor=b3)
×
723
            [bc.apply(b3) for bc in self.problem.bd.bcu]
×
724
            solver_3.solve(self.problem.u_s.vector(), b3)
×
725

726
            self.problem.u_s.assign((1.0-sor_vel)*self.problem.u_k + sor_vel*self.problem.u_s)
×
727

728
            # Step 2: Pressure correction step
729
            # Don't need A4 rebuild
730
            b4 = assemble(self.problem.F4_rhs, tensor=b4)
×
731
            [bc.apply(b4) for bc in self.problem.bd.bcp]
×
732
            solver_4.solve(self.problem.dp.vector(), b4)
×
733

734

735
            # Step 3: Velocity correction step
736
            b5 = assemble(self.problem.F5_rhs, tensor=b5)
×
737
            # [bc.apply(b3) for bc in bcu]
738
            solver_5.solve(self.problem.du.vector(), b5)
×
739

740

741
            norm_du = norm(self.problem.du)
×
742
            norm_dp = norm(self.problem.dp)
×
743
            # u_conv.append(norm_du)
744
            # p_conv.append(norm_dp)
745

746
            self.problem.u_k_old.assign(self.problem.u_k)
×
747
            self.problem.p_k_old.assign(self.problem.p_k)
×
748

749
            self.problem.u_k.assign(self.problem.u_s + self.problem.du)
×
750

751
            # p_k.assign(p_k + dp)
752
            # p_k.assign(p_k + sor_pr*dp)
753

754
            if not use_simpler:
×
755
                # p_k.assign(p_k + dp)
756
                self.problem.p_k.assign(self.problem.p_k + sor_pr*self.problem.dp)
×
757

758
            # u_k1.assign(u_k)
759
            # p_k1.assign(p_k)
760

761
            if (iter_num+1) % 1 == 0:
×
762
                self.SaveTimeSeries(iter_num+1)
×
763

764
            toc = time.time()
×
765

766
            if self.params.rank == 0:
×
767
                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))
×
768

769
        outer_toc = time.time()
×
770
        if self.params.rank == 0:
×
771
            print('TOTAL CPU TIME = %f seconds' % (outer_toc - outer_tic))
×
772

773
        ### Evaluate the objectives ###
774
        if self.optimizing or self.save_objective:
×
775
            self.J += self.EvaluateObjective()
×
776
            # self.J += self.objective_func(self,(self.iter_theta-self.problem.dom.inflow_angle))
777
            self.J = self.control_updater(self.J, self.problem)
×
778

779
        if self.save_power and "power":
×
780
            self.EvaulatePowerFunctional()
×
781

782
    @no_annotations
1✔
783
    def SaveTimeSeries(self, simTime):
1✔
784
        # if hasattr(self.problem,"tf_save"):
785
        #     self.problem.tf_save.vector()[:] = 0
786
        #     for fun in self.problem.tf_list:
787
        #         self.problem.tf_save.vector()[:] = self.problem.tf_save.vector()[:] + fun.vector()[:]
788
        # else:
789
        #     self.problem.tf_save = Function(self.problem.fs.V)
790
        #     for fun in self.problem.tf_list:
791
        #         self.problem.tf_save.vector()[:] = self.problem.tf_save.vector()[:] + fun.vector()[:]
792

793

794
        if self.first_save:
×
795
            self.velocity_file = self.params.Save(self.problem.u_k,"velocity",subfolder="timeSeries/",val=simTime)
×
796
            self.pressure_file   = self.params.Save(self.problem.p_k,"pressure",subfolder="timeSeries/",val=simTime)
×
797
            # self.turb_force_file   = self.params.Save(self.problem.tf_save,"turbine_force",subfolder="timeSeries/",val=simTime)
798
            # if self.optimizing:
799
                # self.adj_tape_file = XDMFFile(self.params.folder+"timeSeries/global_adjoint.xdmf")
800
                # self.problem.u_k1.assign(Marker(self.problem.u_k,simTime,self.adj_tape_file))
801
            self.first_save = False
×
802
        else:
803
            self.params.Save(self.problem.u_k,"velocity",subfolder="timeSeries/",val=simTime,file=self.velocity_file)
×
804
            self.params.Save(self.problem.p_k,"pressure",subfolder="timeSeries/",val=simTime,file=self.pressure_file)
×
805
            # self.params.Save(self.problem.tf_save,"turbine_force",subfolder="timeSeries/",val=simTime,file=self.turb_force_file)
806

807
# ================================================================
808

809
class UnsteadySolver(GenericSolver):
1✔
810
    """
811
    This solver is for solving an unsteady problem.  As such, it contains
812
    additional time-stepping features and functions not present in other solvers.
813
    This solver can only be used if an unsteady problem has been specified in
814
    the input file.
815

816
    Args:
817
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
818
    """
819
    def __init__(self,problem):
1✔
820
        super(UnsteadySolver, self).__init__(problem)
1✔
821

822
    # ================================================================
823

824
    def Solve(self):
1✔
825
        # Start the unsteady solver ONLY if an unsteady problem has been created
826
        if self.problem.params["problem"]["type"] == 'unsteady':
1✔
827
            self.fprint("Solving with UnsteadySolver", special="header")
1✔
828
        else:
829
            raise ValueError("UnsteadySolver can only be run with ProblemType = unsteady, not %s" \
×
830
                % (self.problem.params["problem"]["type"]))
831

832
        if type(self.final_time) == str and self.final_time.lower() == 'none':
1✔
833
            self.pseudo_steady = True
×
834
            self.fprint('Found option "None" for final_time, ')
×
835
            self.fprint('Running until unsteady solver is converged.')
×
836
            self.final_time = 1000000.0
×
837
            #self.final_time = 10.0
838

839
        # ================================================================
840

841
        # Start a counter for the total simulation time
842
        self.fprint("dt: %.4f" % (self.problem.dt))
1✔
843
        self.fprint("Final Time: %.1f" % (self.final_time))
1✔
844

845
        # Calculate the time to start recording if optimizing
846
        if self.optimizing:
1✔
847
            if self.record_time == "computed":
1✔
848
                self.record_time = 1.0*(self.wake_RD*self.problem.farm.RD[0]/(self.problem.bd.HH_vel*0.75))
×
849
                if self.final_time < self.record_time + 60.0/self.problem.rpm:
×
850
                    self.fprint("Warning: Final time is too small... overriding")
×
851
                    self.final_time = self.record_time + 60.0/self.problem.rpm
×
852
                    self.fprint("         New Final Time: {:1.2f} s".format(self.final_time))
×
853
            elif self.record_time == "last":
1✔
854
                self.record_time = self.final_time
×
855
            self.fprint("Recording Time: %.1f" % (self.record_time))
1✔
856
            # self.record_time = 250.0
857
            # self.record_time = self.final_time-2.0*saveInterval
858
        else:
859
            self.record_time = 0.0
1✔
860
        self.problem.record_time = self.record_time
1✔
861

862
        # Calculate what time to start the averaging process
863
        init_average = True
1✔
864
        if self.pseudo_steady:
1✔
865
            two_flowthrough_time = 2.0*(self.problem.dom.x_range[1] - self.problem.dom.x_range[0])/self.params['boundary_conditions']['HH_vel']
×
866
            average_start_time = two_flowthrough_time
×
867
            self.fprint('Start averaging after two flow-throughs, or %.1f seconds' % (two_flowthrough_time))
×
868
        else:
869
            average_start_time = 5.0
1✔
870

871
        # ================================================================
872

873
        # Start a counter for the number of saved files
874
        saveCount = 0
1✔
875
        save_next_timestep = False
1✔
876

877
        # Generate file pointers for saved output
878
        # FIXME: This should use the .save method
879
        # fp = []
880
        # fp.append(File("%s/timeSeries/velocity.pvd" % (self.problem.dom.params.folder)))
881
        # fp.append(File("%s/timeSeries/pressure.pvd" % (self.problem.dom.params.folder)))
882
        # fp.append(File("%s/timeSeries/nu_T.pvd" % (self.problem.dom.params.folder)))
883

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

887
        # Save first timestep (create file pointers for first call)
888
        self.SaveTimeSeries(self.simTime, 0.0)
1✔
889

890
        self.fprint("Saving Input Data",special="header")
1✔
891
        if "mesh" in self.params.output:
1✔
892
            self.problem.dom.Save(val=self.iter_val)
1✔
893
        if "initial_guess" in self.params.output:
1✔
894
            self.problem.bd.SaveInitialGuess(val=self.iter_val)
1✔
895
        if "height" in self.params.output and self.problem.dom.dim == 3:
1✔
896
            self.problem.bd.SaveHeight(val=self.iter_val)
1✔
897
        if "turbine_force" in self.params.output:
1✔
898
            self.problem.farm.save_functions(val=self.iter_val)
1✔
899

900
        self.fprint("Finished",special="footer")
1✔
901

902
        # ================================================================
903

904
        self.fprint("")
1✔
905
        self.fprint("Calculating Boundary Conditions")
1✔
906

907
        # FIXME: This should use the boundary information in self.problem.bd.bcs
908
        # bcu, bcp = self.GetBoundaryConditions(0.0)
909
        self.problem.dom.RecomputeBoundaryMarkers(0.0)
1✔
910

911
        # ================================================================
912

913
        self.fprint("Assembling time-independent matrices")
1✔
914

915
        # Assemble left-hand side matrices
916
        A1 = assemble(self.problem.a1)
1✔
917
        A2 = assemble(self.problem.a2)
1✔
918
        A3 = assemble(self.problem.a3)
1✔
919

920
        # Apply boundary conditions to matrices
921
        [bc.apply(A1) for bc in self.problem.bd.bcu]
1✔
922
        [bc.apply(A2) for bc in self.problem.bd.bcp]
1✔
923

924
        # Assemble right-hand side vector
925
        b1 = assemble(self.problem.L1)
1✔
926
        b2 = assemble(self.problem.L2)
1✔
927
        b3 = assemble(self.problem.L3)
1✔
928

929
        # Apply bounday conditions to vectors
930
        [bc.apply(b1) for bc in self.problem.bd.bcu]
1✔
931
        [bc.apply(b2) for bc in self.problem.bd.bcp]
1✔
932

933
        timing = np.zeros(5)
1✔
934

935
        # sol = ['cg', 'bicgstab', 'gmres']
936
        # pre = ['amg', 'hypre_amg', 'hypre_euclid', 'hypre_parasails', 'jacobi', 'petsc_amg', 'sor']
937

938
        # cl_iterator = int(self.params['solver']['cl_iterator'])
939

940
        # if cl_iterator > 0:
941
        #     ind = cl_iterator-1
942
        #     sol_choice = sol[int(np.floor(ind/7))]
943
        #     pre_choice = pre[int(ind%7)]
944

945
        # else:
946
        #     sol_choice = 'gmres'
947
        #     pre_choice = 'default'
948

949
        solver_1 = PETScKrylovSolver('gmres', 'jacobi')
1✔
950
        # solver_1 = PETScKrylovSolver('gmres', 'default')
951
        # solver_1 = PETScKrylovSolver('default', 'default')
952
        solver_1.set_operator(A1)
1✔
953

954
        solver_2 = PETScKrylovSolver('gmres', 'petsc_amg')
1✔
955
        # solver_2 = PETScKrylovSolver('gmres', 'hypre_amg')
956
        # solver_2 = PETScKrylovSolver('default', 'default')
957
        solver_2.set_operator(A2)
1✔
958

959
        solver_3 = PETScKrylovSolver('cg', 'jacobi')
1✔
960
        # solver_3 = PETScKrylovSolver('gmres', 'default')
961
        # solver_3 = PETScKrylovSolver('default', 'default')
962
        solver_3.set_operator(A3)
1✔
963

964
        pr = cProfile.Profile()
1✔
965

966
        # ================================================================
967

968
        self.fprint('Turbine Parameters', special='header')
1✔
969
        self.fprint('Hub Height: %.1f' % (self.problem.farm.turbines[0].HH))
1✔
970
        self.fprint('Yaw: %.4f' % (self.problem.farm.turbines[0].yaw))
1✔
971
        self.fprint('Radius: %.1f' % (self.problem.farm.turbines[0].radius))
1✔
972
        self.fprint('', special='footer')
1✔
973

974
        self.fprint("Solving",special="header")
1✔
975
        self.fprint("Sim Time | Next dt | U_max")
1✔
976
        self.fprint("--------------------------")
1✔
977

978
        def save_adj(adj):
1✔
979
            # print(len(self.adj_time_list),self.adj_time_iter)
980
            if self.adj_time_iter < len(self.adj_time_list):
×
981
                t = np.flip(self.adj_time_list)[self.adj_time_iter]
×
982
                # print(t,t % self.save_interval)
983
                if t % self.save_interval == 0:
×
984
                    adj.rename("adjoint","adjoint")
×
985
                    self.adj_file.write(adj,t)
×
986
            self.adj_time_iter += 1
×
987

988
        # Initialize need loop objects
989
        start = time.time()
1✔
990
        # self.problem.dt_sum = 1.0
991
        self.problem.dt_sum = 0.0
1✔
992
        J_old = 0
1✔
993
        J_diff_old = 100000
1✔
994
        min_count = 0
1✔
995
        simIter = 0
1✔
996
        stable = False
1✔
997

998
        if self.problem.farm.turbines[0].type == "line" or self.problem.farm.turbines[0].type == "dolfin_line":
1✔
999
            tip_speed = self.problem.farm.turbines[0].rpm*2.0*np.pi*self.problem.farm.turbines[0].radius/60.0
1✔
1000
        else:
1001
            tip_speed = 0.0
×
1002

1003
        # self.problem.alm_power_sum = 0.0
1004

1005
        while not stable and self.simTime < self.final_time:
1✔
1006

1007
            # add a fake block that allows us to update the control while dolfin_adjoint is doing it's thing
1008
            # Need to allow processes which don't own the above points to fail gracefully
1009
            # try:
1010
            #     print(f"u(0, 0,150):   {self.problem.u_k([0.0, 0.0,150.0])}")
1011
            # except:
1012
            #     pass
1013
            # try:
1014
            #     print(f"u(0, 0,210):   {self.problem.u_k([0.0, 0.0,210.0])}")
1015
            # except:
1016
            #     pass
1017
            # try:
1018
            #     print(f"u(0,60,150):   {self.problem.u_k([0.0,60.0,150.0])}")
1019
            # except:
1020
            #     pass
1021
            # print(f"max(u):        {self.problem.u_k.vector().max()}")
1022
            # print(f"min(u):        {self.problem.u_k.vector().min()}")
1023
            # print(f"integral(u_x): {assemble(self.problem.u_k[0]*dx)}")
1024
            self.J = self.control_updater(self.J, self.problem, time=self.simTime)
1✔
1025

1026
            self.problem.bd.UpdateVelocity(self.simTime)
1✔
1027

1028
            # Record the "old" max velocity (before this update)
1029
            u_max_k1 = max(tip_speed, self.problem.u_k.vector().max())
1✔
1030

1031
            # Step 1: Tentative velocity step
1032
            tic = time.time()
1✔
1033
            # solve(self.problem.a1==self.problem.L1, self.problem.u_k, bcs=self.problem.bd.bcu)
1034
            # solve(self.problem.a1==self.problem.L1, self.problem.u_k, bcs=self.problem.bd.bcu, solver_parameters={"linear_solver": "gmres","preconditioner": "jacobi"})
1035

1036
            b1 = assemble(self.problem.L1, tensor=b1)
1✔
1037
            [bc.apply(b1) for bc in self.problem.bd.bcu]
1✔
1038
            if self.optimizing:
1✔
1039
                # solve(A1, self.problem.u_k.vector(), b1, 'gmres', 'default',adj_cb=save_adj)
1040
                # solve(A1, self.problem.u_k.vector(), b1, 'gmres', 'default')
1041
                solver_1.solve(self.problem.u_k.vector(), b1)
1✔
1042
            else:
1043
                # solve(A1, self.problem.u_k.vector(), b1, 'gmres', 'default')
1044
                solver_1.solve(self.problem.u_k.vector(), b1)
1✔
1045
            # print("uk("+repr(self.simTime)+")   = "+repr(np.mean(self.problem.u_k.vector()[:])))
1046
            # print("assemble(func*dx): " + repr(float(assemble(inner(self.problem.u_k,self.problem.u_k)*dx))))
1047
            toc = time.time()
1✔
1048
            timing[0] += toc - tic
1✔
1049

1050
            # Step 2: Pressure correction step
1051
            tic = time.time()
1✔
1052
            # solve(self.problem.a2==self.problem.L2, self.problem.p_k, bcs=self.problem.bd.bcp)
1053
            # solve(self.problem.a2==self.problem.L2, self.problem.p_k, bcs=self.problem.bd.bcp, solver_parameters={"linear_solver": "gmres","preconditioner": "petsc_amg"})
1054

1055
            b2 = assemble(self.problem.L2, tensor=b2)
1✔
1056
            [bc.apply(b2) for bc in self.problem.bd.bcp]
1✔
1057
            if self.optimizing:
1✔
1058
                # solve(A2, self.problem.p_k.vector(), b2, 'gmres', 'hypre_amg')
1059
                solver_2.solve(self.problem.p_k.vector(), b2)
1✔
1060
            else:
1061
                solver_2.solve(self.problem.p_k.vector(), b2)
1✔
1062
            # print("uk("+repr(self.simTime)+")   = "+repr(np.mean(self.problem.p_k.vector()[:])))
1063
            # print("assemble(func*dx): " + repr(float(assemble(inner(self.problem.p_k,self.problem.p_k)*dx))))
1064
            toc = time.time()
1✔
1065
            timing[1] += toc - tic
1✔
1066

1067
            # Step 3: Velocity correction step
1068
            tic = time.time()
1✔
1069
            # solve(self.problem.a3==self.problem.L3, self.problem.u_k)
1070
            # solve(self.problem.a3==self.problem.L3, self.problem.u_k, solver_parameters={"linear_solver": "cg","preconditioner": "jacobi"})
1071

1072
            b3 = assemble(self.problem.L3, tensor=b3)
1✔
1073
            if self.optimizing:
1✔
1074
                # solve(A3, self.problem.u_k.vector(), b3, 'gmres', 'default')
1075
                solver_3.solve(self.problem.u_k.vector(), b3)
1✔
1076
            else:
1077
                solver_3.solve(self.problem.u_k.vector(), b3)
1✔
1078
            # print("uk("+repr(self.simTime)+")   = "+repr(np.mean(self.problem.u_k.vector()[:])))
1079
            # print("assemble(func*dx): " + repr(float(assemble(inner(self.problem.u_k,self.problem.u_k)*dx))))
1080
            toc = time.time()
1✔
1081
            timing[2] += toc - tic
1✔
1082

1083
            # Old <- New update step
1084
            self.problem.u_k2.assign(self.problem.u_k1)
1✔
1085
            self.problem.u_k1.assign(self.problem.u_k)
1✔
1086
            self.problem.p_k1.assign(self.problem.p_k)
1✔
1087

1088
            # Record the updated max velocity
1089
            u_max = max(tip_speed, self.problem.u_k.vector().max())
1✔
1090

1091
            # Update the simulation time
1092
            self.simTime_prev = self.simTime
1✔
1093
            self.simTime += self.problem.dt
1✔
1094

1095
            # Compute Reynolds Stress
1096
            if 'KE_entrainment' in self.objective_type.keys():
1✔
1097
                if self.simTime >= self.u_avg_time and self.simTime < self.record_time:
×
1098
                    self.problem.uk_sum.assign(self.problem.uk_sum+self.problem.dt_c*self.problem.u_k)
×
1099
                elif self.simTime >= self.record_time:
×
1100
                    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))
×
1101

1102
            if self.save_all_timesteps:
1✔
1103
                self.SaveTimeSeries(self.simTime,simIter)
×
1104
            elif save_next_timestep:
1✔
1105
                # Read in new inlet values
1106
                # bcu = self.updateInletVelocityFromFile(saveCount, bcu)
1107

1108
                # Clean up self.simTime to avoid accumulating round-off error
1109
                saveCount += 1
1✔
1110
                self.simTime = self.save_interval*saveCount
1✔
1111

1112
                # Save output files
1113
                # self.SaveTimeSeries(fp, self.simTime)
1114
                self.SaveTimeSeries(self.simTime,simIter)
1✔
1115

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

1119
            # Update the turbine force
1120
            tic = time.time()
1✔
1121
            if self.problem.farm.turbines[0].type == "line" or self.problem.farm.turbines[0].type == "dolfin_line":
1✔
1122

1123
                self.problem.alm_power = 0.0
1✔
1124
                # pass
1125

1126
                # t1 = time.time()
1127
                pr.enable()
1✔
1128
                if 'dolfin' in self.problem.farm.turbines[0].type:
1✔
1129
                    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✔
1130
                else:
1131
                    # updated method from dev, is this necessary? this may not work if there are self.farm.too_many_turbines
1132
                    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✔
1133
                    # 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)
1134
                    # self.problem.tf.assign(new_tf)
1135
                pr.disable()
1✔
1136

1137
                # # t2 = time.time()
1138
                # # print(t2-t1)
1139

1140
                # Power [=] N*m*rads/s
1141
                # self.problem.alm_power = self.problem.rotor_torque*(2.0*np.pi*self.problem.rpm/60.0)
1142
                # self.problem.alm_power_dolfin = self.problem.rotor_torque_dolfin*(2.0*np.pi*self.problem.rpm/60.0)
1143

1144
                # # self.problem.alm_power_sum += self.problem.alm_power*self.problem.dt
1145
                # # self.problem.alm_power_average = self.problem.alm_power_sum/self.simTime
1146

1147
                # self.problem.alm_power_dolfin = self.problem.farm.compute_power(self.problem.u_k, self.problem.dom.inflow_angle)
1148
                # output_str = 'Rotor Power (dolfin, solver): %s MW' % (self.problem.alm_power_dolfin/1.0e6)
1149
                # self.fprint(output_str)
1150

1151
            else:
1152
                # This is a hack to avoid errors when using something other than ALM
1153
                # e.g., actuator disks
1154
                self.problem.alm_power = np.zeros(self.problem.farm.numturbs)
×
1155
                self.problem.alm_power_dolfin = np.zeros(self.problem.farm.numturbs)
×
1156

1157
                # exit()
1158
            toc = time.time()
1✔
1159
            timing[3] += toc - tic
1✔
1160

1161
            if self.simTime > average_start_time:
1✔
1162
                if init_average:
1✔
1163

1164
                    convergence_history = []
1✔
1165

1166
                    average_vel_sum = Function(self.problem.fs.V)
1✔
1167
                    average_vel_1 = Function(self.problem.fs.V)
1✔
1168
                    average_vel_2 = Function(self.problem.fs.V)
1✔
1169

1170
                    average_vel_sum.vector()[:] = self.problem.u_k1.vector()[:]*self.problem.dt_previous
1✔
1171
                    average_vel_1.vector()[:] = average_vel_sum.vector()[:]/(self.simTime-average_start_time)
1✔
1172
                    average_vel_2.vector()[:] = 0.0
1✔
1173

1174
                    average_power = self.problem.alm_power*self.problem.dt
1✔
1175

1176
                    init_average = False
1✔
1177
                else:
1178
                    average_vel_sum.vector()[:] += self.problem.u_k1.vector()[:]*self.problem.dt_previous
1✔
1179

1180
                    average_vel_2.vector()[:] = average_vel_1.vector()[:]
1✔
1181
                    average_vel_1.vector()[:] = average_vel_sum.vector()[:]/(self.simTime-average_start_time)
1✔
1182

1183
                    norm_diff = norm(average_vel_2.vector() - average_vel_1.vector(), 'linf')
1✔
1184
                    self.fprint('Change Between Steps (norm)'+ repr(norm_diff))
1✔
1185

1186

1187
                    if norm_diff < 1e-4:
1✔
1188
                        # If the average is no longer changing, stop this run
1189
                        convergence_history.append(1)
×
1190

1191
                        # Only mark "stable" if the last 2 tests have passed
1192
                        if convergence_history[-1] == 1 and convergence_history[-2] == 1:
×
1193
                            stable = True
×
1194
                    else:
1195
                        convergence_history.append(0)
1✔
1196

1197
                    average_power += self.problem.alm_power*self.problem.dt
1✔
1198

1199

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

1203
            if self.save_objective or (self.optimizing and self.simTime >= self.record_time and not self.pseudo_steady):
1✔
1204
                J_next = self.EvaluateObjective()
1✔
1205

1206
            # Calculate the objective function
1207
            if self.optimizing and self.simTime >= self.record_time and not self.pseudo_steady:
1✔
1208

1209
                # Append the current time step for post production
1210
                self.adj_time_list.append(self.simTime)
1✔
1211

1212
                self.J += float(self.problem.dt)*J_next
1✔
1213
                self.problem.dt_sum += self.problem.dt
1✔
1214
                J_new = float(self.J/self.problem.dt_sum)
1✔
1215

1216
                ### TODO, replace this with an actual stabilization criteria such as relative difference tolerance
1217
                # Check the change in J with respect to time and check if we are "stable" i.e. hit the required number of minimums
1218
                J_diff = J_new-J_old
1✔
1219
                # if J_diff_old <= 0 and J_diff > 0 and self.min_total:
1220
                #     if min_count == self.min_total:
1221
                #         stable = True
1222
                #     else:
1223
                #         min_count += 1
1224
                # J_diff_old = J_diff
1225
                J_old = J_new
1✔
1226

1227
                # Another stable checking method that just looks at the difference
1228
                # if abs(J_diff) <= 0.001:
1229
                #     stable = True
1230

1231
                self.fprint("Current Objective Value: "+repr(float(self.J/self.problem.dt_sum)))
1✔
1232
                self.fprint("Change in Objective    : "+repr(float(J_diff)))
1✔
1233

1234

1235

1236
            # to only call the power functional once, check if a) the objective is the power, b) that we are before record time
1237
            if self.save_power:
1✔
1238
                self.EvaulatePowerFunctional()
1✔
1239

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

1243
            tic = time.time()
1✔
1244
            A1 = assemble(self.problem.a1, tensor=A1)
1✔
1245
            [bc.apply(A1) for bc in self.problem.bd.bcu]
1✔
1246
            solver_1.set_operator(A1)
1✔
1247
            toc = time.time()
1✔
1248
            timing[4] += toc - tic
1✔
1249

1250
            # Print some solver statistics
1251
            self.fprint("%8.2f | %7.2f | %5.2f" % (self.simTime, self.problem.dt, u_max))
1✔
1252
            simIter+=1
1✔
1253

1254
        if self.pseudo_steady:
1✔
1255
            self.J = self.EvaluateObjective()
×
1256

1257
        elif (self.optimizing or self.save_objective):
1✔
1258
            # if self.problem.dt_sum > 0.0:
1259
            self.J = self.J/float(self.problem.dt_sum)
1✔
1260

1261

1262
        if self.simTime > average_start_time:
1✔
1263
            average_vel_sum.vector()[:] = average_vel_sum.vector()[:]/(self.simTime-average_start_time)
1✔
1264
            # fp = File('./output/%s/average_vel_sum.pvd' % (self.params.name))
1265
            fp = File(f"{self.params.folder}timeSeries/average_vel_sum.pvd")
1✔
1266
            average_vel_sum.rename('average_vel_sum', 'average_vel_sum')
1✔
1267
            fp << average_vel_sum
1✔
1268

1269
            average_power = average_power/(self.simTime-average_start_time)
1✔
1270
            # self.fprint('AVERAGE Rotor Power: %.6f MW' % (average_power/1e6))
1271
            try:
1✔
1272
                output_str = 'AVERAGE Rotor Power: %s MW' % (np.array2string(average_power/1.0e6, precision=9, separator=', '))
1✔
1273
            except:
×
1274
                output_str = 'AVERAGE Rotor Power: %s MW' % (average_power)
×
1275
            self.fprint(output_str)
1✔
1276

1277
        # add a fake block that allows us to update the control while dolfin_adjoint is doing it's thing
1278
        # try:
1279
        #     print(f"u(0, 0,150):   {self.problem.u_k([0.0, 0.0,150.0])}")
1280
        # except:
1281
        #     pass
1282
        # try:
1283
        #     print(f"u(0, 0,210):   {self.problem.u_k([0.0, 0.0,210.0])}")
1284
        # except:
1285
        #     pass
1286
        # try:
1287
        #     print(f"u(0,60,150):   {self.problem.u_k([0.0,60.0,150.0])}")
1288
        # except:
1289
        #     pass
1290

1291
        # print(f"max(u):        {self.problem.u_k.vector().max()}")
1292
        # print(f"min(u):        {self.problem.u_k.vector().min()}")
1293
        # print(f"integral(u_x): {assemble(self.problem.u_k[0]*dx)}")
1294
        self.J = self.control_updater(self.J, self.problem, time=self.simTime)
1✔
1295
        stop = time.time()
1✔
1296

1297
        self.fprint('================================================================')
1✔
1298
        # self.fprint('Solver:              %s' % (sol_choice))
1299
        # self.fprint('Preconditioner:      %s' % (pre_choice))
1300
        total_time = stop - start
1✔
1301
        self.fprint('Assembling A1:       %.2f sec (%.1f%%)' % (timing[4], 100.0*timing[4]/total_time))
1✔
1302
        self.fprint('Tentative Velocity:  %.2f sec (%.1f%%)' % (timing[0], 100.0*timing[0]/total_time))
1✔
1303
        self.fprint('Pressure Correction: %.2f sec (%.1f%%)' % (timing[1], 100.0*timing[1]/total_time))
1✔
1304
        self.fprint('Velocity Update:     %.2f sec (%.1f%%)' % (timing[2], 100.0*timing[2]/total_time))
1✔
1305
        self.fprint('ALM Calculation:     %.2f sec (%.1f%%)' % (timing[3], 100.0*timing[3]/total_time))
1✔
1306
        self.fprint('================================================================')
1✔
1307
        self.fprint("Finished",special="footer")
1✔
1308
        self.fprint("Solve Complete: {:1.2f} s".format(stop-start),special="footer")
1✔
1309

1310
        # if self.params.rank == 0:
1311
        #     # pr.print_stats(25, sort = 'time')
1312
        #     ps = pstats.Stats(pr).strip_dirs().sort_stats('cumulative')
1313
        #     ps.print_stats(50)
1314
        #     pr.dump_stats('%s/profiling_alm.prof' % (self.params.folder))
1315

1316

1317
    # ================================================================
1318
    @no_annotations
1✔
1319
    def SaveTimeSeries(self, simTime, simIter=None):
1✔
1320

1321
        self.DebugOutput(simTime,simIter)
1✔
1322
        ### TODO THIS NEED TO BE CLEAN TO ACCOUNT FOR DISKS
1323

1324
        if self.problem.farm.turbines[0].type == "line":
1✔
1325
            if hasattr(self.problem,"tf_save"):
1✔
1326
                self.problem.tf_save.vector()[:] = 0
1✔
1327
                for fun in self.problem.farm.turbines:
1✔
1328
                    self.problem.tf_save.vector()[:] = self.problem.tf_save.vector()[:] + fun.tf.vector()[:]
1✔
1329
            else:
1330
                self.problem.tf_save = Function(self.problem.fs.V)
1✔
1331
                for fun in self.problem.farm.turbines:
1✔
1332
                    self.problem.tf_save.vector()[:] = self.problem.tf_save.vector()[:] + fun.tf.vector()[:]
1✔
1333
        else:
1334
            if hasattr(self.problem,"tf_save"):
1✔
1335
                self.problem.tf_save = project(sum(self.problem.farm.tf_list), self.problem.fs.V, solver_type='cg')
1✔
1336
            else:
1337
                self.problem.tf_save = Function(self.problem.fs.V)
1✔
1338
                self.problem.tf_save = project(sum(self.problem.farm.tf_list), self.problem.fs.V, solver_type='cg')
1✔
1339

1340

1341
        if self.first_save:
1✔
1342
            self.velocity_file = self.params.Save(self.problem.u_k,"velocity",subfolder="timeSeries/",val=simTime)
1✔
1343
            self.pressure_file   = self.params.Save(self.problem.p_k,"pressure",subfolder="timeSeries/",val=simTime)
1✔
1344
            self.turb_force_file   = self.params.Save(self.problem.tf_save,"turbine_force",subfolder="timeSeries/",val=simTime)
1✔
1345
            # if self.optimizing:
1346
                # self.adj_tape_file = XDMFFile(self.params.folder+"timeSeries/global_adjoint.xdmf")
1347
                # self.problem.u_k1.assign(Marker(self.problem.u_k,simTime,self.adj_tape_file))
1348
            self.first_save = False
1✔
1349
        else:
1350
            self.params.Save(self.problem.u_k,"velocity",subfolder="timeSeries/",val=simTime,file=self.velocity_file)
1✔
1351
            self.params.Save(self.problem.p_k,"pressure",subfolder="timeSeries/",val=simTime,file=self.pressure_file)
1✔
1352
            self.params.Save(self.problem.tf_save,"turbine_force",subfolder="timeSeries/",val=simTime,file=self.turb_force_file)
1✔
1353
            # if self.optimizing:
1354
                # self.problem.u_k1.assign(Marker(self.problem.u_k,simTime,self.adj_tape_file))
1355

1356
        # # Save velocity files (pointer in fp[0])
1357
        # self.problem.u_k.rename('Velocity', 'Velocity')
1358
        # fp[0] << (self.problem.u_k, simTime)
1359

1360
        # # Save pressure files (pointer in fp[1])
1361
        # self.problem.p_k.rename('Pressure', 'Pressure')
1362
        # fp[1] << (self.problem.p_k, simTime)
1363

1364
        # # Save eddy viscosity files (pointer in fp[2])
1365
        # # nu_T_val = project(self.problem.nu_T, self.problem.fs.Q, solver_type='gmres')
1366
        # # nu_T_val.rename('nu_T', 'nu_T')
1367
        # # fp[2] << (nu_T_val, simTime)
1368

1369
        # # Save turbine force files (pointer in fp[3])
1370
        # # if "turbine_force" in self.params.output:
1371

1372
        # workaround = False
1373

1374
        # if workaround:
1375
        #     tf_value = project(self.problem.tf, self.problem.fs.V, solver_type='gmres')
1376
        #     tf_value.rename('Turbine_Force', 'Turbine_Force')
1377
        #     fp[3] << (tf_value, simTime)
1378
        # else:
1379
        #     self.problem.tf.rename('Turbine_Force', 'Turbine_Force')
1380
        #     fp[3] << (self.problem.tf, simTime)
1381

1382

1383
    # ================================================================
1384

1385
    def AdjustTimestepSize(self, save_next_timestep, saveInterval, simTime, u_max, u_max_k1):
1✔
1386
        # Save the old dt for reference
1387
        self.problem.dt_previous = self.problem.dt
1✔
1388

1389
        # Set the CFL target (0.2 is a good value for stability and speed, YMMV)
1390
        # cfl_target = 0.5
1391
        cfl_target = 1.0
1✔
1392
        cfl_target = float(self.problem.params["solver"]["cfl_target"])
1✔
1393

1394
        # Enforce a minimum timestep size
1395
        dt_min = 0.01
1✔
1396

1397
        # Calculate the change in velocity using a first-order, backward difference
1398
        dudt = u_max - u_max_k1
1✔
1399

1400
        # Calculate the projected velocity
1401
        u_max_projected = u_max + dudt
1✔
1402

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

1406
        # Move to larger dt slowly (smaller dt happens instantly)
1407
        if dt_new > self.problem.dt:
1✔
1408
            # Amount of new dt to use: 0 = none, 1 = all
1409
            SOR = 0.5
1✔
1410
            dt_new = SOR*dt_new + (1.0-SOR)*self.problem.dt
1✔
1411

1412
        # dt_new = dt_new/2.0
1413

1414
        # Calculate the time remaining until the next file output
1415
        time_remaining = saveInterval - (simTime % saveInterval)
1✔
1416

1417
        # If the new timestep would jump past a save point, modify the new timestep size
1418
        if not save_next_timestep and dt_new + dt_min >= time_remaining:
1✔
1419
            dt_new = time_remaining
1✔
1420
            save_next_timestep = True
1✔
1421
        else:
1422
            save_next_timestep = False
1✔
1423

1424
        # dt_new = 0.04
1425
        if self.params.num_procs > 1:
1✔
1426
            dt_new_vec = np.zeros(self.params.num_procs, dtype=np.float64)
×
1427
            dt_new = np.array(dt_new, dtype=np.float64)
×
1428
            self.params.comm.Allgather(dt_new, dt_new_vec)
×
1429
            dt_new = np.amin(dt_new_vec)
×
1430

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

1433
        # Update both the Python variable and FEniCS constant
1434
        self.problem.dt = dt_new
1✔
1435
        self.problem.dt_c.assign(dt_new)
1✔
1436

1437

1438
        # float(self.problem.dt_c) # to get the regular ol' variable
1439

1440
        return save_next_timestep
1✔
1441

1442
    # ================================================================
1443

1444
    def UpdateActuatorLineForce(self, simTime):
1✔
1445

1446
        def rot_x(theta):
×
1447
            Rx = np.array([[1, 0, 0],
×
1448
                           [0, np.cos(theta), -np.sin(theta)],
1449
                           [0, np.sin(theta), np.cos(theta)]])
1450

1451
            return Rx
×
1452

1453
        def rot_y(theta):
×
1454
            Ry = np.array([[np.cos(theta), 0, np.sin(theta)],
×
1455
                           [0, 1, 0],
1456
                           [-np.sin(theta), 0, np.cos(theta)]])
1457

1458
            return Ry
×
1459

1460
        def rot_z(theta):
×
1461
            Rz = np.array([[np.cos(theta), -np.sin(theta), 0],
×
1462
                           [np.sin(theta), np.cos(theta), 0],
1463
                           [0, 0, 1]])
1464

1465
            return Rz
×
1466

1467
        #================================================================
1468
        # Get Mesh Properties
1469
        #================================================================
1470

1471
        ndim = self.problem.dom.dim
×
1472

1473
        # Get the coordinates of the vector function space
1474
        coords = self.problem.fs.V.tabulate_dof_coordinates()
×
1475
        coords = np.copy(coords[0::self.problem.dom.dim, :])
×
1476

1477

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

1481
        #================================================================
1482
        # Set Turbine and Fluid Properties
1483
        #================================================================
1484

1485
        # Set the density
1486
        rho = 1.0
×
1487

1488
        # Set the hub height
1489
        hub_height = self.problem.farm.HH[0] # For a SWIFT turbine
×
1490

1491
        # Get the hub-height velocity
1492
        u_inf = 8.0
×
1493

1494
        # Set the rotational speed of the turbine
1495
        RPM = 15.0
×
1496

1497
        # Set the yaw of the turbine
1498
        yaw = self.problem.farm.yaw[0]
×
1499

1500
        # Set the number of blades in the turbine
1501
        num_blades = 3
×
1502

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

1506
        # Chord length
1507
        c = L/20.0
×
1508

1509
        # Width of Gaussian
1510
        # eps = 2.5*c
1511
        eps = 2.0*self.problem.dom.mesh.hmin()/np.sqrt(3)
×
1512

1513
        # print(self.problem.farm.x)
1514
        # print(self.problem.farm.y)
1515
        # print(self.problem.farm.HH)
1516
        # print(self.problem.farm.yaw)
1517
        # print(self.problem.farm.RD)
1518
        # print(self.problem.farm.radius)
1519

1520
        # Discretize each blade into separate nodes
1521
        num_actuator_nodes = 10
×
1522

1523
        #================================================================
1524
        # Set Derived Constants
1525
        #================================================================
1526

1527
        # Calculate the blade velocity
1528
        period = 60.0/RPM
×
1529
        tip_speed = np.pi*2.0*L*RPM/60.0
×
1530
        blade_vel = np.vstack((np.zeros(num_actuator_nodes),
×
1531
                               np.zeros(num_actuator_nodes),
1532
                               np.linspace(0.0, tip_speed, num_actuator_nodes)))
1533

1534
        # Set the initial angle of each blade
1535
        theta_vec = np.linspace(0.0, 2.0*np.pi, num_blades+1)
×
1536
        theta_vec = theta_vec[0:num_blades]
×
1537

1538
        # Calculate discrete node positions
1539
        rdim = np.linspace(0.0, L, num_actuator_nodes)
×
1540

1541
        # Calculate width of individual blade segment
1542
        w = rdim[1] - rdim[0]
×
1543

1544
        # Calculate an array describing the x, y, z position of each point
1545
        xblade = np.vstack((np.zeros(num_actuator_nodes),
×
1546
                            rdim,
1547
                            np.zeros(num_actuator_nodes)))
1548

1549
        #================================================================
1550
        # Begin Calculating Turbine Forces
1551
        #================================================================
1552

1553
        # Lift and drag coefficient (could be an array and you interpolate the value based on R)
1554
        # cl_dolf = Constant((np.linspace(1.5, 0.5, num_actuator_nodes)))
1555
        # cd_dolf = Constant((np.ones(num_actuator_nodes)))
1556
        # cl = cl_dolf.values()
1557
        # cd = cd_dolf.values()
1558

1559
        cl = np.linspace(0.0, 2.0, num_actuator_nodes) # Uncomment for controllability study
×
1560
        cd = np.linspace(2.0, 0.0, num_actuator_nodes)
×
1561
        # cl = np.linspace(2.0, 0.0, num_actuator_nodes) # Uncomment for controllability study
1562
        # cd = np.linspace(0.0, 2.0, num_actuator_nodes)
1563
        # cl = np.ones(num_actuator_nodes)
1564
        # cd = np.ones(num_actuator_nodes)
1565

1566

1567
        # Create space to hold the vector values
1568
        tf_vec = np.zeros(np.size(coords))
×
1569
        lift_force = np.zeros((np.shape(coords)[0], ndim))
×
1570
        drag_force = np.zeros((np.shape(coords)[0], ndim))
×
1571

1572
        # tf_lift_vec = np.zeros(np.size(coords))
1573
        # tf_drag_vec = np.zeros(np.size(coords))
1574

1575
        # Calculate the blade position based on current simTime and turbine RPM
1576
        theta_offset = simTime/period*2.0*np.pi
×
1577

1578
        # Treat each blade separately
1579
        for theta_0 in theta_vec:
×
1580
            theta = theta_0 + theta_offset
×
1581

1582
            # Generate a rotation matrix for this turbine blade
1583
            Rx = rot_x(theta)
×
1584
            Rz = rot_z(yaw)
×
1585

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

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

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

1596
            # Add together to get |x^2 + y^2 + z^2|^2
1597
            dist2 = dx_full[0::ndim] + dx_full[1::ndim] + dx_full[2::ndim]
×
1598

1599
            # Set if using local velocity around inidividual nodes
1600
            using_local_velocity = False
×
1601

1602
            if using_local_velocity:
×
1603
                # Generate the fluid velocity from the actual node locations in the flow
1604
                u_fluid = np.zeros((3, num_actuator_nodes))
×
1605

1606
                for k in range(num_actuator_nodes):
×
1607
                    u_fluid[:, k] = self.problem.u_k1(xblade_rotated[0, k],
×
1608
                                                      xblade_rotated[1, k],
1609
                                                      xblade_rotated[2, k])
1610

1611
            else:
1612
                # Generate the fluid velocity analytically using the hub height velocity
1613
                # u_inf_vec = u_inf*np.ones(num_actuator_nodes)
1614

1615
                # u_fluid = np.vstack((u_inf_vec,
1616
                #                      np.zeros(num_actuator_nodes),
1617
                #                      np.zeros(num_actuator_nodes)))
1618
                u_fluid = np.zeros((3, num_actuator_nodes))
×
1619

1620
                for k in range(num_actuator_nodes):
×
1621
                    u_fluid[0, k] = 8.0*(xblade_rotated[2, k]/hub_height)**0.18
×
1622

1623

1624
            # Rotate the blade velocity in the global x, y, z, coordinate system
1625
            blade_vel_rotated = np.dot(Rz, np.dot(Rx, -blade_vel))
×
1626

1627
            # Form the total relative velocity vector (including velocity from rotating blade)
1628
            u_rel = u_fluid + blade_vel_rotated
×
1629

1630
            # Create unit vectors in the direction of u_rel
1631
            u_rel_mag = np.linalg.norm(u_rel, axis=0)
×
1632
            u_rel_mag[u_rel_mag < 1e-6] = 1e-6
×
1633
            u_unit = u_rel/u_rel_mag
×
1634

1635
            # Calculate the lift and drag forces using the relative velocity magnitude
1636
            lift = (0.5*cl*rho*c*w*u_rel_mag**2)/(eps**3 * np.pi**1.5)
×
1637
            drag = (0.5*cd*rho*c*w*u_rel_mag**2)/(eps**3 * np.pi**1.5)
×
1638

1639
            # Calculate the force at every mesh point due to every node [numGridPts x NumActuators]
1640
            nodal_lift = lift*np.exp(-dist2/eps**2)
×
1641
            nodal_drag = drag*np.exp(-dist2/eps**2)
×
1642

1643
            # Calculate a vector in the direction of the blade
NEW
1644
            blade_unit = xblade_rotated[:, -1] - np.array([0.0, 0.0, hub_height])
×
1645

UNCOV
1646
            for k in range(num_actuator_nodes):
×
1647
                # The drag unit simply points opposite the relative velocity unit vector
1648
                drag_unit = -u_unit[:, k]
×
1649

1650
                # The lift is normal to the plane generated by the blade and relative velocity
1651
                lift_unit = np.cross(drag_unit, blade_unit)
×
1652
                lift_unit_mag = np.linalg.norm(lift_unit)
×
1653
                if lift_unit_mag < 1e-6:
×
1654
                    lift_unit_mag = 1e-6
×
1655
                lift_unit = lift_unit/lift_unit_mag
×
1656

1657
                vector_nodal_drag = np.outer(nodal_drag[:, k], drag_unit)
×
1658
                vector_nodal_lift = np.outer(nodal_lift[:, k], lift_unit)
×
1659

1660
                drag_force += vector_nodal_drag
×
1661
                lift_force += vector_nodal_lift
×
1662

1663
            # The total turbine force is the sum of lift and drag effects
1664
        turbine_force = drag_force + lift_force
×
1665

1666
        for k in range(ndim):
×
1667
            tf_vec[k::ndim] = turbine_force[:, k]
×
1668
            # tf_lift_vec[k::ndim] += lift_force[:, k]
1669
            # tf_drag_vec[k::ndim] += drag_force[:, k]
1670

1671
        # Save the output
1672
        tf_vec[np.abs(tf_vec) < 1e-12] = 0.0
×
1673
        # tf_lift_vec[np.abs(tf_lift_vec) < 1e-12] = 0.0
1674
        # tf_drag_vec[np.abs(tf_drag_vec) < 1e-12] = 0.0
1675

1676
        self.problem.tf.vector()[:] = tf_vec
×
1677

1678
    # ================================================================
1679

1680
    def UpdateActuatorLineForceOld(self, simTime):
1✔
1681
        coords = self.problem.fs.V.tabulate_dof_coordinates()
×
1682
        coords = np.copy(coords[0::self.problem.dom.dim, :])
×
1683

1684
        # Set up the turbine geometry
1685
        num_blades = 3
×
1686
        theta_vec = np.linspace(0, 2.0*np.pi, num_blades+1)
×
1687
        theta_vec = theta_vec[0:num_blades]
×
1688

1689
        # print(theta_vec)
1690

1691
        # Lift and drag coefficient (could be an array and you interpolate the value based on R)
1692
        cl = 1.0
×
1693
        cd = 2.0
×
1694

1695
        rho = 1
×
1696

1697
        u_inf = 8
×
1698

1699
        # Blade length (turbine radius)
1700
        L = 13.5
×
1701

1702
        # Chord length
1703
        c = L/20
×
1704

1705
        # Number of blade evaluation sections
1706
        num_actuator_nodes = 50
×
1707
        rdim = np.linspace(0, L, num_actuator_nodes)
×
1708
        zdim = 0.0 + np.zeros(num_actuator_nodes)
×
1709
        xblade = np.vstack((np.zeros(num_actuator_nodes), rdim, zdim))
×
1710

1711
        # Width of individual blade segment
1712
        w = rdim[1] - rdim[0]
×
1713

1714
        # Width of Gaussian
1715
        eps = 2.5*c
×
1716

1717
        tf_vec = np.zeros(np.size(coords))
×
1718

1719

1720

1721

1722
        RPM = 15.0
×
1723
        period = 60.0/RPM
×
1724
        theta_offset = simTime/period*2.0*np.pi
×
1725

1726
        tip_speed = np.pi*2*L*RPM/60
×
1727

1728
        blade_vel = np.linspace(0.0, tip_speed, num_actuator_nodes)
×
1729

1730

1731
        constant_vel_mag = True
×
1732

1733
        if constant_vel_mag:
×
1734
            # Lift and drag force (calculated outside loop since cl, cd, u_inf, and c assumed constant)
1735
            lift = 0.5*cl*rho*u_inf**2*c*w
×
1736
            drag = 0.5*cd*rho*u_inf**2*c*w
×
1737

1738
            # Save time by moving calculation out of loop
1739
            L1 = lift/(eps**3 * np.pi**1.5)
×
1740
            D1 = drag/(eps**3 * np.pi**1.5)
×
1741
        else:
1742
            # Lift and drag force (calculated outside loop since cl, cd, u_inf, and c assumed constant)
1743
            u_inf = u_inf**2 + blade_vel**2
×
1744
            lift = 0.5*cl*rho*c*w*u_inf
×
1745
            drag = 0.5*cd*rho*c*w*u_inf
×
1746

1747
            # Save time by moving calculation out of loop
1748
            L1 = lift/(eps**3 * np.pi**1.5)
×
1749
            D1 = drag/(eps**3 * np.pi**1.5)
×
1750

1751

1752

1753
        for theta_0 in theta_vec:
×
1754
            theta = theta_0 + theta_offset
×
1755

1756
            # Create rotation matrix for this turbine blade
1757
            rotA = np.array([[1, 0, 0],
×
1758
                             [0, np.cos(theta), -np.sin(theta)],
1759
                             [0, np.sin(theta), np.cos(theta)]])
1760

1761
            # Rotate the entire [x; y; z] matrix using this matrix
1762
            xblade_rotated = np.dot(rotA, xblade)
×
1763
            xblade_rotated[2, :] += 32.1
×
1764

1765
            use_vectorized_calculation = True
×
1766

1767
            if use_vectorized_calculation:
×
1768
                coordsLinear = np.copy(coords.reshape(-1, 1))
×
1769

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

1772
                dx_full = (coordsLinear - xblade_rotated_full)**2
×
1773

1774
                dist2 = dx_full[0::self.problem.dom.dim] + \
×
1775
                dx_full[1::self.problem.dom.dim] + \
1776
                dx_full[2::self.problem.dom.dim]
1777

1778
                total_dist2_lift = np.sum(L1*np.exp(-dist2/eps**2), axis = 1)
×
1779
                total_dist2_drag = np.sum(D1*np.exp(-dist2/eps**2), axis = 1)
×
1780

1781
                tf_vec[0::self.problem.dom.dim] += -total_dist2_drag
×
1782
                tf_vec[1::self.problem.dom.dim] += total_dist2_lift*np.sin(theta)
×
1783
                tf_vec[2::self.problem.dom.dim] += -total_dist2_lift*np.cos(theta)
×
1784

1785
            else:
1786
                for k, x in enumerate(coords):
×
1787
                    # Flip row into column
1788
                    xT = x.reshape(-1, 1)
×
1789

1790
                    # Subtract this 3x1 point from the 3xN array of rotated turbine segments
1791
                    dx = (xT - xblade_rotated)**2
×
1792
                    mag = np.sum(dx, axis = 0)
×
1793

1794
                    # Add up the contribution from each blade segment
1795
                    lift_sum = np.sum(L1*np.exp(-mag/eps**2))
×
1796
                    drag_sum = np.sum(D1*np.exp(-mag/eps**2))
×
1797

1798
                    # Store the individual vector components using linear index
1799
                    tf_vec[3*k+0] += -drag_sum
×
1800
                    tf_vec[3*k+1] += lift_sum*np.sin(theta)
×
1801
                    tf_vec[3*k+2] += -lift_sum*np.cos(theta)
×
1802

1803
        tf_vec[np.abs(tf_vec) < 1e-12] = 0.0
×
1804

1805
        self.problem.tf.vector()[:] = tf_vec
×
1806

1807
    # ================================================================
1808

1809
    def UpdateTurbineForce(self, simTime, turbsPerPlatform):
1✔
1810
        coords = self.problem.fs.V.tabulate_dof_coordinates()
×
1811
        coords = np.copy(coords[0::self.problem.dom.dim, :])
×
1812

1813
        # Pre allocate all numpy arrays and vectors
1814
        tf_array = np.zeros(np.shape(coords))
×
1815
        tf_vec = np.zeros(np.size(tf_array))
×
1816
        xs = np.zeros(np.shape(coords))
×
1817

1818
        # Radius of the two "arms" measured from the hinge
1819
        rad = 189.0
×
1820

1821
        if turbsPerPlatform == 1:
×
1822
            rad = 0.0
×
1823

1824
        # Angle defined between the two "arms"
1825
        phi = np.pi/3.0
×
1826

1827
        # Calculate the offset from the hinge to each turbine
1828
        xp_offset = rad*np.cos(phi/2.0)
×
1829
        yp_offset = rad*np.sin(phi/2.0)
×
1830

1831
        # delta_yaw = 0.0
1832

1833
        for k in range(self.problem.farm.numturbs):
×
1834
            # Position of the kth turbune
1835
            xpos = float(self.problem.farm.mx[k])
×
1836
            ypos = float(self.problem.farm.my[k])
×
1837

1838
            if self.problem.dom.dim == 2:
×
1839
                x0 = np.array([xpos, ypos])
×
1840
            else:
1841
                zpos = float(self.problem.farm.mz[k])
×
1842
                x0 = np.array([xpos, ypos, zpos])
×
1843

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

1849
            yaw = float(self.problem.farm.myaw[k] + delta_yaw)
×
1850
            W = float(self.problem.farm.W[k]/2.0)
×
1851
            R = float(self.problem.farm.RD[k]/2.0)
×
1852
            ma = float(self.problem.farm.ma[k])
×
1853

1854
            # Create a rotation matrix for this yaw angle
1855
            A_rotation = self.RotationMatrix(yaw)
×
1856

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

1860
            for doublet in range(turbsPerPlatform):
×
1861

1862
                offset = np.zeros(self.problem.dom.dim)
×
1863
                offset[0] = xp_offset
×
1864
                offset[1] = yp_offset*(-1)**doublet
×
1865

1866
                # Offset each turbine from the center of rotation
1867
                xs = xs0 - offset
×
1868

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

1873
                # Tangential to blades: Create the function that represents the Disk of the turbine
1874
                D_norm = 2.884512175878827
×
1875
                if self.problem.dom.dim == 2:
×
1876
                    D1 = (xs[:, 1]/R)**2.0
×
1877
                else:
1878
                    D1 = (xs[:, 1]/R)**2.0 + (xs[:, 2]/R)**2.0
×
1879

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

1882
                # Create the function that represents the force
1883
                if self.problem.dom.dim == 2:
×
1884
                    r = xs[:, 1]
×
1885
                else:
1886
                    r = np.sqrt(xs[:, 1]**2.0 + xs[:, 2]**2.0)
×
1887

1888
                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
×
1889

1890
                u_vec = self.problem.u_k1.vector()[:]
×
1891
                ux = u_vec[0::self.problem.dom.dim]
×
1892
                uy = u_vec[1::self.problem.dom.dim]
×
1893
                uD = ux*np.cos(yaw) + uy*np.sin(yaw)
×
1894

1895
                tf_array[:, 0] = tf_array[:, 0] + F*T*D*np.cos(yaw)*uD**2.0
×
1896
                tf_array[:, 1] = tf_array[:, 1] + F*T*D*np.sin(yaw)*uD**2.0
×
1897

1898

1899
        # Riffle shuffle the array elements into a FEniCS-style vector
1900
        for k in range(self.problem.dom.dim):
×
1901
            tf_vec[k::self.problem.dom.dim] = tf_array[:, k]
×
1902

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

1905
        # Set the vector elements
1906
        self.problem.tf.vector()[:] = tf_vec
×
1907

1908
    # ================================================================
1909

1910
    def RotationMatrix(self, yaw):
1✔
1911
        cosYaw = np.cos(yaw)
×
1912
        sinYaw = np.sin(yaw)
×
1913

1914
        if self.problem.dom.dim == 2:
×
1915
            A_rotation = np.array([[cosYaw, -sinYaw],
×
1916
                                   [sinYaw,  cosYaw]])
1917
        else:
1918
            A_rotation = np.array([[cosYaw, -sinYaw, 0.0],
×
1919
                                   [sinYaw,  cosYaw, 0.0],
1920
                                   [   0.0,     0.0, 1.0]])
1921

1922
        return A_rotation
×
1923

1924
    # ================================================================
1925

1926

1927
    def modifyInletVelocity(self, simTime, bcu):
1✔
1928

1929
        # Define tolerance
1930
        tol = 1e-6
×
1931

1932
        def left_wall(x, on_boundary):
×
1933
            return on_boundary and x[0] < self.problem.dom.x_range[0] + tol
×
1934

1935
        HH_vel = self.problem.bd.HH_vel
×
1936

1937
        # Get the coordinates using the vector funtion space, V
1938
        coords = self.problem.fs.V.tabulate_dof_coordinates()
×
1939
        coords = np.copy(coords[0::self.problem.dom.dim, :])
×
1940

1941
        # Create a function representing to the inlet velocity
1942
        vel_inlet_func = Function(self.problem.fs.V)
×
1943

1944
        inlet_type = 1
×
1945

1946
        if inlet_type == 1:
×
1947
            # Create arrays for the steady, vortex, and combined velocities
1948
            vel_steady = np.zeros(np.shape(coords))
×
1949
            vel_steady[:, 0] = HH_vel
×
1950
            # print(HH_vel)
1951

1952
            vel_vort = np.zeros(np.shape(coords))
×
1953
            vel_inlet = np.zeros(np.shape(coords))
×
1954

1955
            # Specify the vortex radius
1956
            vortRad = 1000
×
1957
            vortRad2 = vortRad**2
×
1958

1959
            # Specify the vortex velocity and calculate its position from the starting point
1960
            vortVel = 1.0
×
1961

1962
            period = 1000.0
×
1963
            xd = period/2 - vortVel*(simTime%period)
×
1964

1965
            fac = 0.1
×
1966
            sep = 650
×
1967
            Tau = 1000
×
1968

1969
            for k, x in enumerate(coords):
×
1970
                if x[0] < self.problem.dom.x_range[0] + tol:
×
1971

1972
                    # xd should replace x[0] in the following equations
1973
                    if np.abs(xd) < 1e-3:
×
1974
                        xd = 1e-3
×
1975

1976
                    cp = ((x[1] + sep/2)**2 + xd**2)/(4*Tau)
×
1977
                    cn = ((x[1] - sep/2)**2 + xd**2)/(4*Tau)
×
1978

1979
                    # U-velocity
1980
                    vel_inlet[k, 0] = fac*((1 - np.exp(-cp))/cp*(x[1] + sep/2) -\
×
1981
                                           (1 - np.exp(-cn))/cn*(x[1] - sep/2)) + 1
1982

1983
                    # V-velocity
1984
                    vel_inlet[k, 1] = fac*(-(1 - np.exp(-cp))/cp*xd +\
×
1985
                                            (1 - np.exp(-cn))/cn*xd)
1986

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

1989
                    if norm > 10.0:
×
1990
                        vel_inlet[k, 0] = vel_inlet[k, 0]/norm*10.0
×
1991
                        vel_inlet[k, 1] = vel_inlet[k, 1]/norm*10.0
×
1992

1993
                    # dx = x - vortPos
1994
                    # dist2 = dx[0]*dx[0] + dx[1]*dx[1]
1995

1996
                    # if dist2 < vortRad2:
1997
                    #     theta = np.arctan2(dx[1], dx[0])
1998
                    #     fac = 1.0 - np.sqrt(dist2/vortRad2)
1999
                    #     vel_vort[k, 0] = -np.sin(theta)
2000
                    #     vel_vort[k, 1] = np.cos(theta)
2001
                    # else:
2002
                    #     fac = 0.0
2003
                    #     vel_vort[k, 0] = 0.0
2004
                    #     vel_vort[k, 1] = 0.0
2005

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

2008
        elif inlet_type == 2:
×
2009
            jet_rad = 400
×
2010

2011
            vel_inlet = np.zeros(np.shape(coords))
×
2012

2013
            for k, x in enumerate(coords):
×
2014
                if x[0] < self.problem.dom.x_range[0] + tol:
×
2015
                    if np.abs(x[1]) < jet_rad:
×
2016
                        thetaMax = 15.0/180.0*np.pi
×
2017

2018
                        theta = thetaMax*np.sin(simTime/1000*2*np.pi)
×
2019

2020
                        vel_inlet[k, 0] = 2.0*HH_vel*np.cos(theta)
×
2021
                        vel_inlet[k, 1] = 2.0*HH_vel*np.sin(theta)
×
2022
                    else:
2023
                        vel_inlet[k, 0] = HH_vel
×
2024
                        vel_inlet[k, 1] = 0.0
×
2025

2026

2027
        # Riffle shuffle the array elements into a 1D vector
2028
        vel_inlet_vector = np.zeros(np.size(vel_inlet))
×
2029

2030
        for k in range(self.problem.dom.dim):
×
2031
            vel_inlet_vector[k::self.problem.dom.dim] = vel_inlet[:, k]
×
2032

2033
        # Assign the function the vector of values
2034
        vel_inlet_func.vector()[:] = vel_inlet_vector
×
2035

2036

2037
        # Update the inlet velocity
2038
        bcu[0] = DirichletBC(self.problem.fs.V, vel_inlet_func, left_wall)
×
2039

2040
        return bcu
×
2041

2042

2043
# ================================================================
2044

2045
class MultiAngleSolver(SteadySolver):
1✔
2046
    """
2047
    This solver will solve the problem using the steady state solver for every
2048
    angle in angles.
2049

2050
    Args:
2051
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
2052
        angles (list): A list of wind inflow directions.
2053
    """
2054

2055
    def __init__(self,problem):
1✔
2056
        super(MultiAngleSolver, self).__init__(problem)
1✔
2057
        if self.params["domain"]["type"] in ["imported"]:
1✔
2058
            raise ValueError("Cannot use a Multi-Angle Solver with an "+self.params["domain"]["type"]+" domain.")
×
2059
        self.orignal_solve = super(MultiAngleSolver, self).Solve
1✔
2060
        if self.problem.dom.raw_inflow_angle is None:
1✔
2061
            self.wind_range = [0, 2.0*np.pi,self.num_wind_angles]
×
2062
        elif isinstance(self.problem.dom.raw_inflow_angle,list):
1✔
2063
            if len(self.problem.dom.raw_inflow_angle)==3:
1✔
2064
                self.wind_range = self.problem.dom.raw_inflow_angle
1✔
2065
            else:
2066
                self.wind_range = [self.problem.dom.raw_inflow_angle[0],self.problem.dom.raw_inflow_angle[1],self.num_wind_angles]
×
2067
        else:
2068
            self.wind_range = [self.problem.dom.raw_inflow_angle,self.problem.dom.raw_inflow_angle+2.0*np.pi,self.num_wind_angles]
1✔
2069

2070
        self.angles = np.linspace(*self.wind_range,endpoint=self.endpoint)
1✔
2071
        self.angles = meteor_to_math(self.angles)
1✔
2072
        # self.angles += self.angle_offset
2073

2074
    def Solve(self):
1✔
2075
        for i, theta in enumerate(self.angles):
1✔
2076
            self.fprint("Performing Solve {:d} of {:d}".format(i+1,len(self.angles)),special="header")
1✔
2077
            self.fprint("Wind Angle: "+repr(math_to_meteor(theta)))
1✔
2078
            if i > 0 or not near(theta,self.problem.dom.inflow_angle):
1✔
2079
                self.problem.dom.inflow_angle = theta
1✔
2080
                self.ChangeWindAngle(theta)
1✔
2081
            self.iter_val = math_to_meteor(theta)
1✔
2082
            self.orignal_solve()
1✔
2083
            self.fprint("Finished Solve {:d} of {:d}".format(i+1,len(self.angles)),special="footer")
1✔
2084

2085
class TimeSeriesSolver(SteadySolver):
1✔
2086
    """
2087
    This solver will solve the problem using the steady state solver for every
2088
    angle in angles.
2089

2090
    Args:
2091
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
2092
        angles (list): A list of wind inflow directions.
2093
    """
2094

2095
    def __init__(self,problem):
1✔
2096
        super(TimeSeriesSolver, self).__init__(problem)
×
2097
        if self.params["domain"]["type"] in ["imported"]:
×
2098
            raise ValueError("Cannot use a Multi-Angle Solver with an "+self.params["domain"]["type"]+" domain.")
×
2099
        self.orignal_solve = super(TimeSeriesSolver, self).Solve
×
2100

2101
        raw_data = np.loadtxt(self.velocity_path,comments="#")
×
2102
        self.times = raw_data[:,0]
×
2103
        self.speeds = raw_data[:,1]
×
2104
        self.angles = raw_data[:,2]
×
2105
        self.num_solve = len(self.speeds)
×
2106

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

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

2125

2126

2127

2128

2129

2130

2131

2132

2133

2134

2135

2136

2137

2138

2139

2140

2141

2142

2143

2144

2145

2146

2147

2148

2149
##### OLD CODE FOR FUTURE REFERENCE ####
2150

2151

2152
    # # def CalculatePowerFunctional_n(self,inflow_angle = 0.0):
2153
    # def CalculatePowerFunctional(self,inflow_angle = 0.0):
2154

2155
    #     # if self.problem.dom.dim == 3:
2156
    #     if self.problem.dom.dim <= 3:
2157
    #         # print("here")
2158
    #         ### Reconstruct Turbine Force ###
2159
    #         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]
2160

2161

2162
    #         W = self.problem.farm.thickness[0]*1.0
2163
    #         R = self.problem.farm.RD[0]/2.0
2164

2165
    #         T_norm = 2.0*gamma(7.0/6.0)
2166
    #         D_norm = pi*gamma(4.0/3.0)
2167
    #         volNormalization_3D = T_norm*D_norm*W*R**2.0
2168
    #         print("3D Volume: "+repr(volNormalization_3D))
2169

2170
    #         T_norm = 2.0*gamma(7.0/6.0)
2171
    #         D_norm = 2.0*gamma(7.0/6.0)
2172
    #         volNormalization_2D = T_norm*D_norm*W*R
2173
    #         print("3D Volume: "+repr(volNormalization_2D))
2174

2175

2176
    #         if self.problem.dom.dim == 2:
2177
    #             dim_scale = volNormalization_3D/volNormalization_2D
2178
    #             print(dim_scale)
2179
    #             dim_scale = pi*R/2
2180
    #             print(dim_scale)
2181
    #             dim_scale = 2*R*R*volNormalization_2D/volNormalization_3D
2182
    #             print(dim_scale)
2183
    #         else:
2184
    #             dim_scale = 1.0
2185

2186
    #         ### Calculate Power ###
2187
    #         J = dot(tf*dim_scale,self.u_next)*dx
2188

2189
    #         ### Save Individual Powers ###
2190
    #         if self.save_power:
2191
    #             J_list=np.zeros(self.problem.farm.numturbs+1)
2192
    #             J_list[-1]=assemble(J,**self.extra_kwarg)
2193

2194
    #             if self.problem.farm.actuator_disks_list is not None:
2195
    #                 for i in range(self.problem.farm.numturbs):
2196
    #                     yaw = self.problem.farm.myaw[i]+inflow_angle
2197
    #                     tf1 = self.problem.farm.actuator_disks_list[i] * cos(yaw)**2
2198
    #                     tf2 = self.problem.farm.actuator_disks_list[i] * sin(yaw)**2
2199
    #                     tf3 = self.problem.farm.actuator_disks_list[i] * 2.0 * cos(yaw) * sin(yaw)
2200
    #                     tf = tf1*self.u_next[0]**2+tf2*self.u_next[1]**2+tf3*self.u_next[0]*self.u_next[1]
2201
    #                     J_list[i] = assemble(dot(tf*dim_scale,self.u_next)*dx,**self.extra_kwarg)
2202

2203
    #             for j in J_list:
2204
    #                 print(j)
2205
    #             # exit()
2206
    #     # else:
2207
    #     #     J=0
2208
    #     #     J_list=np.zeros(self.problem.farm.numturbs+1)
2209
    #     #     for i in range(self.problem.farm.numturbs):
2210

2211
    #     #         ### Set some Values ###
2212
    #     #         yaw = self.problem.farm.myaw[i]+inflow_angle
2213
    #     #         W = self.problem.farm.W[i]
2214
    #     #         R = self.problem.farm.RD[i]/2.0
2215
    #     #         A = pi*R**2.0
2216
    #     #         a = self.problem.farm.ma[i]
2217
    #     #         C_tprime = 4*a/(1-a)
2218
    #     #         C_pprime = 0.45/(1-a)**3
2219

2220
    #     #         ### Define Integral Constants ###
2221
    #     #         T_norm = 2.0*gamma(7.0/6.0)
2222
    #     #         D_norm = 2.0*gamma(7.0/6.0)
2223
    #     #         h1 = float(hyper([],[1/3, 2/3, 5/6, 7/6],-(np.pi**6/46656)))
2224
    #     #         h2 = float(hyper([],[2/3, 7/6, 4/3, 3/2],-(np.pi**6/46656)))
2225
    #     #         h3 = float(hyper([],[4/3, 3/2, 5/3, 11/6],-(np.pi**6/46656)))
2226
    #     #         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)
2227

2228
    #     #         ### Reconstruct Turbine Force ###
2229
    #     #         tf1 = self.problem.farm.actuator_disks_list[i] * cos(yaw)**2
2230
    #     #         tf2 = self.problem.farm.actuator_disks_list[i] * sin(yaw)**2
2231
    #     #         tf3 = self.problem.farm.actuator_disks_list[i] * 2.0 * cos(yaw) * sin(yaw)
2232
    #     #         tf = tf1*self.u_next[0]**2+tf2*self.u_next[1]**2+tf3*self.u_next[0]*self.u_next[1]
2233
    #     #         tf = tf*T_norm*D_norm*W*R
2234

2235
    #     #         ### Calculate Volume of the Domain ###
2236
    #     #         unit = Function(self.problem.fs.Q)
2237
    #     #         unit.vector()[:] = 1.0
2238
    #     #         vol = assemble(unit*dx)
2239

2240
    #     #         ### Calculate Integral of Actuator Disk ###
2241
    #     #         if self.problem.farm.force == "constant":
2242
    #     #             Va = C_tprime*T_norm*D_norm*W*R*R
2243
    #     #         elif self.problem.farm.force == "sine":
2244
    #     #             Va = C_tprime*T_norm*SD_norm*W*R*R
2245

2246
    #     #         ### Calculate Power ###
2247
    #     #         # J_current = 0.5*A*C_pprime*(assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2248
    #     #         # J_current = 0.5*A*C_pprime/vol*(dot(tf,self.u_next)/Va)**3*dx
2249
    #     #         J_current = 0.5*A*C_pprime/vol*(dot(tf,self.u_next)/Va)*dx
2250
    #     #         # J_current = 0.5*A*C_pprime/vol*(unit/Va)**3*dx
2251
    #     #         # J_current = unit*0.5*A*C_pprime/vol*(1/Va)**3*dx
2252
    #     #         # J_current = unit*Va*dx
2253
    #     #         J += J_current
2254

2255
    #     #         if self.save_power:
2256
    #     #             J_list[i] = assemble(J_current)
2257

2258
    #     #     if self.save_power:
2259
    #     #         J_list[-1]=np.sum(J_list[:-1])
2260
    #     #         print()
2261
    #     #         for j in J_list:
2262
    #     #             print(j)
2263
    #     #         # exit()
2264

2265
    #         folder_string = self.params.folder+"/data/"
2266
    #         if not os.path.exists(folder_string): os.makedirs(folder_string)
2267

2268
    #         if self.J_saved:
2269
    #             f = open(folder_string+"power_data.txt",'ab')
2270
    #         else:
2271
    #             f = open(folder_string+"power_data.txt",'wb')
2272
    #             self.J_saved = True
2273

2274
    #         np.savetxt(f,[J_list])
2275
    #         f.close()
2276

2277
    #     return J
2278

2279
    # # def CalculatePowerFunctional_o(self,delta_yaw = 0.0):
2280
    # # # def CalculatePowerFunctional(self,delta_yaw = 0.0):
2281
    # #     self.fprint("Computing Power Functional")
2282

2283
    # #     x=SpatialCoordinate(self.problem.dom.mesh)
2284
    # #     J=0.
2285
    # #     J_list=np.zeros(self.problem.farm.numturbs+1)
2286
    # #     for i in range(self.problem.farm.numturbs):
2287

2288
    # #         mx = self.problem.farm.mx[i]
2289
    # #         my = self.problem.farm.my[i]
2290
    # #         mz = self.problem.farm.mz[i]
2291
    # #         x0 = [mx,my,mz]
2292
    # #         W = self.problem.farm.thickness[i]*1.0
2293
    # #         R = self.problem.farm.RD[i]/2.0
2294
    # #         ma = self.problem.farm.ma[i]
2295
    # #         yaw = self.problem.farm.myaw[i]+delta_yaw
2296
    # #         u = self.u_next
2297
    # #         A = pi*R**2.0
2298
    # #         C_tprime = 4*ma/(1-ma)
2299
    # #         C_pprime = 0.45/(1-ma)**3
2300

2301
    # #         ### Rotate and Shift the Turbine ###
2302
    # #         xs = self.problem.farm.YawTurbine(x,x0,yaw)
2303
    # #         u_d = u[0]*cos(yaw) + u[1]*sin(yaw)
2304

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

2308
    # #         if self.problem.dom.dim == 3:
2309
    # #             # WTGbase = Expression(("cos(yaw)","sin(yaw)","0.0"),yaw=yaw,degree=1)
2310
    # #             WTGbase = as_vector((cos(yaw),sin(yaw),0.0))
2311

2312
    # #             ### Create the function that represents the Disk of the turbine
2313
    # #             r = sqrt(xs[1]**2.0+xs[2]**2.0)/R
2314
    # #             D = exp(-pow(r,6.0))
2315
    # #             # D = exp(-pow((pow((xs[1]/R),2)+pow((xs[2]/R),2)),6.0))
2316

2317
    # #             volNormalization = assemble(T*D*dx)
2318
    # #             print(volNormalization)
2319
    # #             T_norm = 2.0*gamma(7.0/6.0)
2320
    # #             D_norm = pi*gamma(4.0/3.0)
2321
    # #             volNormalization = T_norm*D_norm*W*R**2.0
2322
    # #             print(volNormalization)
2323

2324
    # #             ### Create the function that represents the force ###
2325
    # #             if self.problem.farm.force == "constant":
2326
    # #                 F = 0.5*A*C_tprime
2327
    # #             elif self.problem.farm.force == "sine":
2328
    # #                 # r = sqrt(xs[1]**2.0+xs[2]**2)
2329
    # #                 F = 0.5*A*C_tprime*(r*sin(pi*r)+0.5)/(.81831)
2330

2331
    # #             J_current = dot(F*T*D*WTGbase*u_d**2,u)*dx
2332
    # #             J += J_current
2333
    # #             if self.save_power:
2334
    # #                 J_list[i] = assemble(J_current)
2335

2336
    # #         else:
2337
    # #             # WTGbase = Expression(("cos(yaw)","sin(yaw)"),yaw=yaw,degree=1)
2338
    # #             WTGbase = as_vector((cos(yaw),sin(yaw)))
2339

2340
    # #             ### Create the function that represents the Disk of the turbine
2341
    # #             r = sqrt(xs[1]**2.0+xs[2]**2.0)/R
2342
    # #             D = exp(-pow(r,6.0))
2343

2344

2345
    # #             # T_norm = 2.0*gamma(7.0/6.0)
2346
    # #             # D_norm = 2.0*gamma(7.0/6.0)
2347
    # #             # h1 = float(hyper([],[1/3, 2/3, 5/6, 7/6],-(np.pi**6/46656)))
2348
    # #             # h2 = float(hyper([],[2/3, 7/6, 4/3, 3/2],-(np.pi**6/46656)))
2349
    # #             # h3 = float(hyper([],[4/3, 3/2, 5/3, 11/6],-(np.pi**6/46656)))
2350
    # #             # 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)
2351
    # #             # volNormalization = T_norm*SD_norm*W*R**(self.problem.dom.dim)
2352
    # #             volNormalization = assemble(T*D*dx)
2353

2354
    # #             T_norm = 2.0*gamma(7.0/6.0)
2355
    # #             D_norm = pi*gamma(4.0/3.0)
2356
    # #             volNormalization_3D = T_norm*D_norm*W*R**2.0
2357
    # #             # print(volNormalization_3D)
2358

2359

2360
    # #             T_norm = 2.0*gamma(7.0/6.0)
2361
    # #             D_norm = 2.0*gamma(7.0/6.0)
2362
    # #             volNormalization_2D = T_norm*D_norm*W*R
2363
    # #             # print(volNormalization_2D)
2364

2365

2366
    # #             print(volNormalization,volNormalization_2D,volNormalization_3D,2*volNormalization_2D/volNormalization_3D)
2367

2368
    # #             ### Create the function that represents the force ###
2369
    # #             if self.problem.farm.force == "constant":
2370
    # #                 F = 0.5*self.problem.farm.RD[i]*C_tprime
2371
    # #             elif self.problem.farm.force == "sine":
2372
    # #                 F = 0.5*self.problem.farm.RD[i]*C_tprime*(r*sin(pi*r)+0.5)/(.81831)
2373

2374
    # #             # V  = assemble(F*T*D*dx)
2375
    # #             # Va = float(C_tprime)*T_norm*SD_norm*W*R*R
2376
    # #             # print(V,Va,V/Va,V/(W*R*R),Va/(W*R*R))
2377

2378
    # #             J_current = assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)
2379
    # #             # J_current = (assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)
2380
    # #             # J_current = 0.5*A*C_pprime*(assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)/assemble(F*T*D*dx))
2381
    # #             # print(float(J_current))
2382
    # #             # J_current_old = 0.5*A*C_pprime*(assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2383
    # #             # J_current = 0.5*A*C_pprime*(assemble(T*D*u*dx)/assemble(T*D*dx))**3
2384

2385
    # #             # J_3D = [1692363.167076575,801778.7751333286,545135.3528982735]
2386
    # #             # J_3D = [767698.3159772983,420644.4831798226,291267.477222329]
2387
    # #             # print(float(J_current_old),float(J_current_old/J_current),float(J_3D[i]/J_current))
2388
    # #             # J_current = (assemble(0.5*A*C_pprime*F*T*D*u_d*dx)/assemble(F*T*D*dx))
2389

2390

2391

2392

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

2399

2400

2401

2402

2403

2404

2405

2406

2407

2408

2409

2410

2411

2412
    # #             # J_current = 0.5*A*C_pprime*(1/assemble(F*T*D*dx))**3
2413
    # #             J += J_current
2414
    # #             if self.save_power:
2415
    # #                 # J_list[i] = (assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2416
    # #                 J_list[i] = J_current
2417

2418
    # #     if self.save_power:
2419
    # #         J_list[-1]=np.sum(J_list[:-1])
2420
    # #         print()
2421
    # #         for j in J_list:
2422
    # #             print(j)
2423
    # #         exit()
2424

2425
    # #         folder_string = self.params.folder+"/data/"
2426
    # #         if not os.path.exists(folder_string): os.makedirs(folder_string)
2427

2428
    # #         if self.J_saved:
2429
    # #             f = open(folder_string+"power_data.txt",'ab')
2430
    # #         else:
2431
    # #             f = open(folder_string+"power_data.txt",'wb')
2432
    # #             self.J_saved = True
2433

2434
    # #         np.savetxt(f,[J_list])
2435
    # #         f.close()
2436

2437
    # #     return J
2438

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