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

NREL / WindSE / 5524401473

pending completion
5524401473

push

github

web-flow
Update main (#104)

* minor change to warning text

* shift gauss cyld by RD

* fixed cyld, converted point to expression

* fixed jit problem

* added sharpness param to point blockage

* fixed u_k update for constraints

* fixed taylor test convergence

* Added check_totals option

* fixed constraint derivatives, and taylor test inputs

* ALM DEL method

* default params

* updating yaml params

* moving example to demo folder

* renaming example folder

* starting on acuator disks

* fixed inflow angle bug

* made the multiangle solver more robust

* fixing what the fix broke

* yep

* Added fatpack to the install script

* added pandas

* got dolfin actuator disks working

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

* got numpy disks working

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

* updated some demos/test, fixed objectives

* initial commit to compare to refactored ALM output to dev

* test push upstream

* minor cleaning after verifying results match

* adding power calc and more cleanup

* getting ready for optimization test

* adding test file for collaborating on optimization

* storing before opt testing original branch

* pushing latest mpi_u_fluid deriv testing

* adding latest test updates

* cleaning up ALM block tests

* syncing print statements for jeff

* refactored imported wind farms

* added empty farm, updated demos/test/docs

* fixed alm adjoint bug, got all test running

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

* fixing conflicts before factoring out trans/rots

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

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

* consistent var names prev vs behind

* fix alm gradients

* take two

* fixing merge conflicts

* fixing mz dijitso error

* added missi... (continued)

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

4546 of 7192 relevant lines covered (63.21%)

0.63 hits per line

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

44.44
/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
    ### Check if we need dolfin_adjoint ###
33
    if windse_parameters.dolfin_adjoint:
1✔
34
        from dolfin_adjoint import *
1✔
35
        from windse.blocks import blockify, MarkerBlock, ControlUpdaterBlock
1✔
36
        from pyadjoint import AdjFloat
1✔
37

38
    ### Import objective functions ###
39
    import windse.objective_functions as obj_funcs
1✔
40

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

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

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

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

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

87
        self.extra_kwarg = {}            
1✔
88
        if self.params.dolfin_adjoint:
1✔
89
            self.extra_kwarg["annotate"] = False
1✔
90

91
        J_temp = 0.0
1✔
92
        if self.params["general"]["dolfin_adjoint"]:
1✔
93
            J_temp = AdjFloat(0.0)
1✔
94

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

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

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

116
        #Check if we need to save the power output
117
        if self.save_power:
1✔
118
            self.J = J_temp
1✔
119
            self.J_saved = False
1✔
120

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

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

135
            self.tag_output("min_x_vel"+suffix,ux.vector().min()) # probably not the fastest way to get the average velocity
1✔
136
            self.tag_output("max_x_vel"+suffix,ux.vector().max()) # probably not the fastest way to get the average velocity
1✔
137
            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✔
138
            self.tag_output("min_y_vel"+suffix,uy.vector().min()) # probably not the fastest way to get the average velocity
1✔
139
            self.tag_output("max_y_vel"+suffix,uy.vector().max()) # probably not the fastest way to get the average velocity
1✔
140
            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✔
141
            if self.problem.dom.dim == 3:
1✔
142
                self.tag_output("min_z_vel"+suffix,uz.vector().min()) # probably not the fastest way to get the average velocity
1✔
143
                self.tag_output("max_z_vel"+suffix,uz.vector().max()) # probably not the fastest way to get the average velocity
1✔
144
                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✔
145

146

147

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

156
        u.vector()[:]=u.vector()[:]/self.problem.dom.xscale
1✔
157
        self.problem.dom.mesh.coordinates()[:]=self.problem.dom.mesh.coordinates()[:]/self.problem.dom.xscale
1✔
158

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

177
    def ChangeWindSpeed(self,inflow_speed):
1✔
178
        """
179
        This function recomputes all necessary components for a new wind direction
180

181
        Args: 
182
            theta (float): The new wind angle in radians
183
        """
184
        self.problem.ChangeWindSpeed(inflow_speed)
×
185

186
    def ChangeWindAngle(self,inflow_angle):
1✔
187
        """
188
        This function recomputes all necessary components for a new wind direction
189

190
        Args: 
191
            theta (float): The new wind angle in radians
192
        """
193
        self.problem.ChangeWindAngle(inflow_angle)
×
194

195
    # def SavePower(self,inflow_angle=0.0):
196

197
    #     J_list=np.zeros(self.problem.farm.numturbs+1)
198

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

209
    #     folder_string = self.params.folder+"/data/"
210
    #     if not os.path.exists(folder_string): os.makedirs(folder_string)
211

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

218
    #     np.savetxt(f,[J_list])
219
    #     f.close()
220

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

230

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

235
        first_call = True
1✔
236
        if self.J_saved:
1✔
237
            first_call = False
1✔
238

239
        annotate = self.params.dolfin_adjoint 
1✔
240

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

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

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

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

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

281
    def marker(self, u, simTime, adj_tape_file):
1✔
282
        return u
×
283

284
    def control_updater(self, J, problem, time=None):
1✔
285
        return J
1✔
286

287

288

289

290

291
class SteadySolver(GenericSolver):
1✔
292
    """
293
    This solver is for solving the steady state problem
294

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

302
    def Solve(self):
1✔
303
        """
304
        This solves the problem setup by the problem object.
305
        """
306

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

319
        # exit()
320

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

328
        # ### Setup nonlinear solver ###
329
        # nonlinear_problem = NonlinearVariationalProblem(self.problem.F, self.problem.up_k, self.problem.bd.bcs, J)
330
        # nonlinear_solver  = NonlinearVariationalSolver(nonlinear_problem)
331

332
        # ### Set some parameters ###
333
        # solver_parameters = nonlinear_solver.parameters['newton_solver']['linear_solver'] = 'gmres'
334

335
        # # nonlinear_solver.ksp().setGMRESRestart(40)
336

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

351
        # nonlinear_solver.parameters
352
        # print_nested_dict(nonlinear_solver.parameters, 0)
353
        # exit()
354

355

356

357
        # print(type(solver_parameters))
358

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

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

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

379
            krylov_options = {"absolute_tolerance": 9e-3,
1✔
380
                              "relative_tolerance": 9e-1,
381
                              "maximum_iterations": 5000}
382

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

395
        elif self.nonlinear_solver == "snes":
1✔
396
            # ### Add some helper functions to solver options ###
397

398
            krylov_options = {"absolute_tolerance":  1e-12,
1✔
399
                              "relative_tolerance":  1e-6,
400
                              "maximum_iterations":  5000,
401
                              "monitor_convergence": True}
402

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

415
        else:
416
            raise ValueError("Unknown nonlinear solver type: {0}".format(self.nonlinear_solver))
×
417

418
        ### Start the Solve Process ###
419
        self.fprint("Solving",special="header")
1✔
420
        start = time.time()
1✔
421

422
        # ### Solve the Baseline Problem ###
423
        # solve(self.problem.F_sans_tf == 0, self.problem.up_k, self.problem.bd.bcs, solver_parameters=solver_parameters, **self.extra_kwarg)
424

425
        # ### Store the Baseline and Assign for the real solve ###
426
        # self.up_baseline = self.problem.up_k.copy(deepcopy=True)
427
        # self.problem.up_k.assign(self.up_baseline)
428

429
        ### Solve the real problem ###
430
        # mem0=memory_usage()[0]
431
        # 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)
432
        solve(self.problem.F == 0, self.problem.up_k, self.problem.bd.bcs, solver_parameters=solver_parameters)
1✔
433
        stop = time.time()
1✔
434

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

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

456

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

465
        # self.nu_T = None
466

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

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

480

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

487
        if self.save_power:
1✔
488
            self.EvaulatePowerFunctional()
1✔
489

490
            # print(self.outflow_markers)
491
            # self.J += -dot(self.problem.farm.rotor_disks,self.u_k)*dx
492

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

507
        self.DebugOutput()
1✔
508

509
# 
510
# ================================================================
511

512
class IterativeSteadySolver(GenericSolver):
1✔
513
    """
514
    This solver is for solving the iterative steady state problem
515

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

523
    def Solve(self):
1✔
524
        """
525
        This solves the problem setup by the problem object.
526
        """
527

528
        def set_solver_mode(solver_type):
×
529
            if solver_type == 'steady':
×
530
                self.problem.dt_1.assign(0)
×
531
                self.problem.dt_2.assign(1)
×
532
                self.problem.dt_3.assign(1)
×
533

534
                sor_vel = 0.1
×
535
                sor_pr = 0.2
×
536

537
            elif solver_type == 'unsteady':
×
538
                delta_t = 100.0
×
539

540
                self.problem.dt_1.assign(1.0/delta_t)
×
541
                self.problem.dt_2.assign(1.0/delta_t)
×
542
                self.problem.dt_3.assign(delta_t)
×
543

544
                sor_vel = 1.0
×
545
                sor_pr = 1.0
×
546

547
            else:
548
                raise ValueError('Solver type should be "steady" or "unsteady".')
×
549

550
            return sor_vel, sor_pr
×
551

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

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

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

578
        self.SaveTimeSeries(0)
×
579

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

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

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

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

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

610
        vel_sol_options = ['gmres', 'none']
×
611
        pr_sol_options = ['bicgstab', 'none']
×
612

613
        solver_1 = PETScKrylovSolver(vel_sol_options[0], vel_sol_options[1])
×
614
        solver_1.set_operator(A1)
×
615

616
        solver_2 = PETScKrylovSolver(pr_sol_options[0], pr_sol_options[1])
×
617
        solver_2.set_operator(A2)
×
618

619
        solver_3 = PETScKrylovSolver(vel_sol_options[0], vel_sol_options[1])
×
620
        solver_3.set_operator(A3)
×
621

622
        solver_4 = PETScKrylovSolver(pr_sol_options[0], pr_sol_options[1])
×
623
        solver_4.set_operator(A4)
×
624

625
        solver_5 = PETScKrylovSolver('cg', 'jacobi')
×
626
        solver_5.set_operator(A5)
×
627

628

629

630
        outer_tic = time.time()
×
631

632

633
        solver_type = 'steady'
×
634
        sor_vel, sor_pr = set_solver_mode(solver_type)
×
635

636
        seed_velocity = False
×
637
        seed_pressure = False
×
638

639

640
        if seed_velocity:
×
641
            self.problem.u_k.assign(self.problem.bd.bc_velocity)
×
642
            self.problem.u_k_old.assign(self.problem.bd.bc_velocity)
×
643

644
        if seed_pressure:
×
645
            if self.params.rank == 0:
×
646
                print('Calculating initial pressure from velocity guess.')
×
647

648
            A1 = assemble(self.problem.F1_lhs, tensor=A1)
×
649
            [bc.apply(A1) for bc in self.problem.bd.bcu]
×
650
            solver_1.set_operator(A1)
×
651

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

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

661
            solver_2.solve(self.problem.p_k.vector(), b2)
×
662

663

664

665
        max_iter = 10
×
666

667
        for iter_num in range(max_iter):
×
668
            tic = time.time()
×
669

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

675
            use_simpler = False
×
676

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

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

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

691
                # u_hat.assign((1.0-sor_vel)*u_k + sor_vel*u_hat)
692

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

697
                solver_2.solve(self.problem.p_k.vector(), b2)
×
698

699

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

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

710
            self.problem.u_s.assign((1.0-sor_vel)*self.problem.u_k + sor_vel*self.problem.u_s)
×
711

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

718

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

724

725
            norm_du = norm(self.problem.du)
×
726
            norm_dp = norm(self.problem.dp)
×
727
            # u_conv.append(norm_du)
728
            # p_conv.append(norm_dp)
729

730
            self.problem.u_k_old.assign(self.problem.u_k)
×
731
            self.problem.p_k_old.assign(self.problem.p_k)
×
732

733
            self.problem.u_k.assign(self.problem.u_s + self.problem.du)
×
734

735
            # p_k.assign(p_k + dp)
736
            # p_k.assign(p_k + sor_pr*dp)
737

738
            if not use_simpler:
×
739
                # p_k.assign(p_k + dp)
740
                self.problem.p_k.assign(self.problem.p_k + sor_pr*self.problem.dp)
×
741

742
            # u_k1.assign(u_k)
743
            # p_k1.assign(p_k)
744

745
            if (iter_num+1) % 1 == 0:
×
746
                self.SaveTimeSeries(iter_num+1)
×
747

748
            toc = time.time()
×
749

750
            if self.params.rank == 0:
×
751
                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))
×
752

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

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

763
        if self.save_power and "power":
×
764
            self.EvaulatePowerFunctional()
×
765

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

777

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

791
# ================================================================
792

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

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

806
    # ================================================================
807

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

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

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

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

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

855
        # ================================================================
856

857
        # Start a counter for the number of saved files
858
        saveCount = 0
1✔
859
        save_next_timestep = False
1✔
860

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

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

871
        # Save first timestep (create file pointers for first call)
872
        self.SaveTimeSeries(self.simTime, 0.0)
1✔
873

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

884
        self.fprint("Finished",special="footer")
1✔
885

886
        # ================================================================
887

888
        self.fprint("")
1✔
889
        self.fprint("Calculating Boundary Conditions")
1✔
890

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

895
        # ================================================================
896

897
        self.fprint("Assembling time-independent matrices")
1✔
898

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

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

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

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

917
        timing = np.zeros(5)
1✔
918

919
        # sol = ['cg', 'bicgstab', 'gmres']
920
        # pre = ['amg', 'hypre_amg', 'hypre_euclid', 'hypre_parasails', 'jacobi', 'petsc_amg', 'sor']
921

922
        # cl_iterator = int(self.params['solver']['cl_iterator'])
923

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

929
        # else:
930
        #     sol_choice = 'gmres'
931
        #     pre_choice = 'default'
932

933
        solver_1 = PETScKrylovSolver('gmres', 'jacobi')
1✔
934
        # solver_1 = PETScKrylovSolver('gmres', 'default')
935
        # solver_1 = PETScKrylovSolver('default', 'default')
936
        solver_1.set_operator(A1)
1✔
937

938
        solver_2 = PETScKrylovSolver('gmres', 'petsc_amg')
1✔
939
        # solver_2 = PETScKrylovSolver('gmres', 'hypre_amg')
940
        # solver_2 = PETScKrylovSolver('default', 'default')
941
        solver_2.set_operator(A2)
1✔
942

943
        solver_3 = PETScKrylovSolver('cg', 'jacobi')
1✔
944
        # solver_3 = PETScKrylovSolver('gmres', 'default')
945
        # solver_3 = PETScKrylovSolver('default', 'default')
946
        solver_3.set_operator(A3)
1✔
947

948
        pr = cProfile.Profile()
1✔
949

950
        # ================================================================
951

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

958
        self.fprint("Solving",special="header")
1✔
959
        self.fprint("Sim Time | Next dt | U_max")
1✔
960
        self.fprint("--------------------------")
1✔
961

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

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

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

987
        # self.problem.alm_power_sum = 0.0
988

989
        while not stable and self.simTime < self.final_time:
1✔
990

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

1010
            self.problem.bd.UpdateVelocity(self.simTime)
1✔
1011

1012
            # Record the "old" max velocity (before this update)
1013
            u_max_k1 = max(tip_speed, self.problem.u_k.vector().max())
1✔
1014

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

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

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

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

1067
            # Old <- New update step
1068
            self.problem.u_k2.assign(self.problem.u_k1)
1✔
1069
            self.problem.u_k1.assign(self.problem.u_k)
1✔
1070
            self.problem.p_k1.assign(self.problem.p_k)
1✔
1071

1072
            # Record the updated max velocity
1073
            u_max = max(tip_speed, self.problem.u_k.vector().max())
1✔
1074

1075
            # Update the simulation time
1076
            self.simTime_prev = self.simTime
1✔
1077
            self.simTime += self.problem.dt
1✔
1078

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

1096
                # Save output files
1097
                # self.SaveTimeSeries(fp, self.simTime)
1098
                self.SaveTimeSeries(self.simTime,simIter)
1✔
1099

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

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

1107
                self.problem.alm_power = 0.0
1✔
1108
                # pass
1109

1110
                # t1 = time.time()
1111
                pr.enable()
1✔
1112
                if 'dolfin' in self.problem.farm.turbines[0].type:
1✔
1113
                    self.problem.ComputeTurbineForce(self.problem.u_k, self.problem.bd.inflow_angle, simTime=self.simTime, simTime_prev=self.simTime_prev, dt=self.problem.dt)
1✔
1114
                else:
1115
                    # updated method from dev, is this necessary?
1116
                    self.problem.farm.update_turbine_force(self.problem.u_k, self.problem.bd.inflow_angle, self.problem.fs, simTime=self.simTime, simTime_prev=self.simTime_prev, dt=self.problem.dt)
1✔
1117
                    # new_tf = self.problem.ComputeTurbineForce(self.problem.u_k, self.problem.bd.inflow_angle, simTime=self.simTime, simTime_prev=self.simTime_prev, dt=self.problem.dt)
1118
                    # self.problem.tf.assign(new_tf)
1119
                pr.disable()
1✔
1120

1121
                # # t2 = time.time()
1122
                # # print(t2-t1)
1123

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

1131
                # self.problem.alm_power_dolfin = self.problem.farm.compute_power(self.problem.u_k, self.problem.bd.inflow_angle)
1132
                # output_str = 'Rotor Power (dolfin, solver): %s MW' % (self.problem.alm_power_dolfin/1.0e6)
1133
                # self.fprint(output_str)
1134

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

1141
                # exit()
1142
            toc = time.time()
1✔
1143
            timing[3] += toc - tic
1✔
1144

1145
            if self.simTime > average_start_time:
1✔
1146
                if init_average:
×
1147

1148
                    convergence_history = []
×
1149

1150
                    average_vel_sum = Function(self.problem.fs.V)
×
1151
                    average_vel_1 = Function(self.problem.fs.V)
×
1152
                    average_vel_2 = Function(self.problem.fs.V)
×
1153

1154
                    average_vel_sum.vector()[:] = self.problem.u_k1.vector()[:]*self.problem.dt_previous
×
1155
                    average_vel_1.vector()[:] = average_vel_sum.vector()[:]/(self.simTime-average_start_time)
×
1156
                    average_vel_2.vector()[:] = 0.0
×
1157

1158
                    average_power = self.problem.alm_power*self.problem.dt
×
1159

1160
                    init_average = False
×
1161
                else:
1162
                    average_vel_sum.vector()[:] += self.problem.u_k1.vector()[:]*self.problem.dt_previous
×
1163

1164
                    average_vel_2.vector()[:] = average_vel_1.vector()[:]
×
1165
                    average_vel_1.vector()[:] = average_vel_sum.vector()[:]/(self.simTime-average_start_time)
×
1166

1167
                    norm_diff = norm(average_vel_2.vector() - average_vel_1.vector(), 'linf')
×
1168
                    self.fprint('Change Between Steps (norm)'+ repr(norm_diff))
×
1169

1170

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

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

1181
                    average_power += self.problem.alm_power*self.problem.dt
×
1182

1183

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

1187
            if self.save_objective or (self.optimizing and self.simTime >= self.record_time and not self.pseudo_steady):
1✔
1188
                J_next = self.EvaluateObjective()
1✔
1189

1190
            # Calculate the objective function
1191
            if self.optimizing and self.simTime >= self.record_time and not self.pseudo_steady:
1✔
1192

1193
                # Append the current time step for post production
1194
                self.adj_time_list.append(self.simTime)
1✔
1195

1196
                self.J += float(self.problem.dt)*J_next
1✔
1197
                self.problem.dt_sum += self.problem.dt 
1✔
1198
                J_new = float(self.J/self.problem.dt_sum)
1✔
1199

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

1211
                # Another stable checking method that just looks at the difference
1212
                # if abs(J_diff) <= 0.001:
1213
                #     stable = True
1214

1215
                self.fprint("Current Objective Value: "+repr(float(self.J/self.problem.dt_sum)))
1✔
1216
                self.fprint("Change in Objective    : "+repr(float(J_diff)))
1✔
1217

1218

1219

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

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

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

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

1238
        if self.pseudo_steady:
1✔
1239
            self.J = self.EvaluateObjective()
×
1240

1241
        elif (self.optimizing or self.save_objective):
1✔
1242
            # if self.problem.dt_sum > 0.0:
1243
            self.J = self.J/float(self.problem.dt_sum)
1✔
1244

1245

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

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

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

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

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

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

1300

1301
    # ================================================================
1302
    @no_annotations
1✔
1303
    def SaveTimeSeries(self, simTime, simIter=None):
1✔
1304

1305
        self.DebugOutput(simTime,simIter)
1✔
1306
        ### TODO THIS NEED TO BE CLEAN TO ACCOUNT FOR DISKS
1307

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

1324

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

1340
        # # Save velocity files (pointer in fp[0])
1341
        # self.problem.u_k.rename('Velocity', 'Velocity')
1342
        # fp[0] << (self.problem.u_k, simTime)
1343

1344
        # # Save pressure files (pointer in fp[1])
1345
        # self.problem.p_k.rename('Pressure', 'Pressure')
1346
        # fp[1] << (self.problem.p_k, simTime)
1347

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

1353
        # # Save turbine force files (pointer in fp[3])
1354
        # # if "turbine_force" in self.params.output:
1355

1356
        # workaround = False
1357

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

1366

1367
    # ================================================================
1368

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

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

1378
        # Enforce a minimum timestep size
1379
        dt_min = 0.01
1✔
1380

1381
        # Calculate the change in velocity using a first-order, backward difference
1382
        dudt = u_max - u_max_k1
1✔
1383

1384
        # Calculate the projected velocity
1385
        u_max_projected = u_max + dudt
1✔
1386

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

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

1396
        # dt_new = dt_new/2.0
1397

1398
        # Calculate the time remaining until the next file output
1399
        time_remaining = saveInterval - (simTime % saveInterval)
1✔
1400

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

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

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

1417
        # Update both the Python variable and FEniCS constant
1418
        self.problem.dt = dt_new
1✔
1419
        self.problem.dt_c.assign(dt_new)
1✔
1420

1421

1422
        # float(self.problem.dt_c) # to get the regular ol' variable
1423

1424
        return save_next_timestep
1✔
1425

1426
    # ================================================================
1427

1428
    def UpdateActuatorLineForce(self, simTime):
1✔
1429

1430
        def rot_x(theta):
×
1431
            Rx = np.array([[1, 0, 0],
×
1432
                           [0, np.cos(theta), -np.sin(theta)],
1433
                           [0, np.sin(theta), np.cos(theta)]])
1434

1435
            return Rx
×
1436

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

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

1451
        #================================================================
1452
        # Get Mesh Properties
1453
        #================================================================
1454

1455
        ndim = self.problem.dom.dim
×
1456

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

1461

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

1465
        #================================================================
1466
        # Set Turbine and Fluid Properties
1467
        #================================================================
1468

1469
        # Set the density
1470
        rho = 1.0
×
1471

1472
        # Set the hub height
1473
        hub_height = self.problem.farm.HH[0] # For a SWIFT turbine
×
1474

1475
        # Get the hub-height velocity
1476
        u_inf = 8.0
×
1477

1478
        # Set the rotational speed of the turbine
1479
        RPM = 15.0
×
1480

1481
        # Set the yaw of the turbine
1482
        yaw = self.problem.farm.yaw[0]
×
1483

1484
        # Set the number of blades in the turbine
1485
        num_blades = 3
×
1486

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

1490
        # Chord length
1491
        c = L/20.0
×
1492

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

1497
        # print(self.problem.farm.x)
1498
        # print(self.problem.farm.y)
1499
        # print(self.problem.farm.HH)
1500
        # print(self.problem.farm.yaw)
1501
        # print(self.problem.farm.RD)
1502
        # print(self.problem.farm.radius)
1503

1504
        # Discretize each blade into separate nodes
1505
        num_actuator_nodes = 10
×
1506

1507
        #================================================================
1508
        # Set Derived Constants
1509
        #================================================================
1510

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

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

1522
        # Calculate discrete node positions
1523
        rdim = np.linspace(0.0, L, num_actuator_nodes)
×
1524

1525
        # Calculate width of individual blade segment
1526
        w = rdim[1] - rdim[0]
×
1527

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

1533
        #================================================================
1534
        # Begin Calculating Turbine Forces
1535
        #================================================================
1536

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

1543
        cl = np.linspace(0.0, 2.0, num_actuator_nodes) # Uncomment for controllability study
×
1544
        cd = np.linspace(2.0, 0.0, num_actuator_nodes)
×
1545
        # cl = np.linspace(2.0, 0.0, num_actuator_nodes) # Uncomment for controllability study
1546
        # cd = np.linspace(0.0, 2.0, num_actuator_nodes)
1547
        # cl = np.ones(num_actuator_nodes)
1548
        # cd = np.ones(num_actuator_nodes)
1549

1550

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

1556
        # tf_lift_vec = np.zeros(np.size(coords))
1557
        # tf_drag_vec = np.zeros(np.size(coords))
1558

1559
        # Calculate the blade position based on current simTime and turbine RPM
1560
        theta_offset = simTime/period*2.0*np.pi
×
1561

1562
        # Treat each blade separately
1563
        for theta_0 in theta_vec:
×
1564
            theta = theta_0 + theta_offset
×
1565

1566
            # Generate a rotation matrix for this turbine blade
1567
            Rx = rot_x(theta)
×
1568
            Rz = rot_z(yaw)
×
1569

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

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

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

1580
            # Add together to get |x^2 + y^2 + z^2|^2
1581
            dist2 = dx_full[0::ndim] + dx_full[1::ndim] + dx_full[2::ndim]
×
1582

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

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

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

1650
        for k in range(ndim):
×
1651
            tf_vec[k::ndim] = turbine_force[:, k]
×
1652
            # tf_lift_vec[k::ndim] += lift_force[:, k]
1653
            # tf_drag_vec[k::ndim] += drag_force[:, k]
1654

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

1660
        self.problem.tf.vector()[:] = tf_vec
×
1661

1662
    # ================================================================
1663

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

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

1673
        # print(theta_vec)
1674

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

1679
        rho = 1
×
1680

1681
        u_inf = 8
×
1682

1683
        # Blade length (turbine radius)
1684
        L = 13.5
×
1685

1686
        # Chord length
1687
        c = L/20
×
1688

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

1695
        # Width of individual blade segment
1696
        w = rdim[1] - rdim[0]
×
1697

1698
        # Width of Gaussian
1699
        eps = 2.5*c
×
1700

1701
        tf_vec = np.zeros(np.size(coords))
×
1702

1703

1704

1705

1706
        RPM = 15.0
×
1707
        period = 60.0/RPM
×
1708
        theta_offset = simTime/period*2.0*np.pi
×
1709

1710
        tip_speed = np.pi*2*L*RPM/60
×
1711

1712
        blade_vel = np.linspace(0.0, tip_speed, num_actuator_nodes)
×
1713

1714

1715
        constant_vel_mag = True
×
1716

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

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

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

1735

1736

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

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

1749
            use_vectorized_calculation = True
×
1750

1751
            if use_vectorized_calculation:
×
1752
                coordsLinear = np.copy(coords.reshape(-1, 1))
×
1753

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

1756
                dx_full = (coordsLinear - xblade_rotated_full)**2
×
1757

1758
                dist2 = dx_full[0::self.problem.dom.dim] + \
×
1759
                dx_full[1::self.problem.dom.dim] + \
1760
                dx_full[2::self.problem.dom.dim]
1761

1762
                total_dist2_lift = np.sum(L1*np.exp(-dist2/eps**2), axis = 1)
×
1763
                total_dist2_drag = np.sum(D1*np.exp(-dist2/eps**2), axis = 1)
×
1764

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

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

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

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

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

1789
        self.problem.tf.vector()[:] = tf_vec
×
1790

1791
    # ================================================================
1792

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

1797
        # Pre allocate all numpy arrays and vectors
1798
        tf_array = np.zeros(np.shape(coords))
×
1799
        tf_vec = np.zeros(np.size(tf_array))
×
1800
        xs = np.zeros(np.shape(coords))
×
1801

1802
        # Radius of the two "arms" measured from the hinge
1803
        rad = 189.0
×
1804

1805
        if turbsPerPlatform == 1:
×
1806
            rad = 0.0
×
1807

1808
        # Angle defined between the two "arms"
1809
        phi = np.pi/3.0
×
1810

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

1815
        # delta_yaw = 0.0
1816

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

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

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

1838
            # Create a rotation matrix for this yaw angle
1839
            A_rotation = self.RotationMatrix(yaw)
×
1840

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

1844
            for doublet in range(turbsPerPlatform):
×
1845

1846
                offset = np.zeros(self.problem.dom.dim)
×
1847
                offset[0] = xp_offset
×
1848
                offset[1] = yp_offset*(-1)**doublet
×
1849

1850
                # Offset each turbine from the center of rotation
1851
                xs = xs0 - offset
×
1852

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

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

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

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

1872
                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
×
1873

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

1879
                tf_array[:, 0] = tf_array[:, 0] + F*T*D*np.cos(yaw)*uD**2.0
×
1880
                tf_array[:, 1] = tf_array[:, 1] + F*T*D*np.sin(yaw)*uD**2.0
×
1881

1882

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

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

1889
        # Set the vector elements
1890
        self.problem.tf.vector()[:] = tf_vec
×
1891

1892
    # ================================================================
1893

1894
    def RotationMatrix(self, yaw):
1✔
1895
        cosYaw = np.cos(yaw)
×
1896
        sinYaw = np.sin(yaw)
×
1897

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

1906
        return A_rotation
×
1907

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

1910

1911
    def modifyInletVelocity(self, simTime, bcu):
1✔
1912

1913
        # Define tolerance
1914
        tol = 1e-6
×
1915

1916
        def left_wall(x, on_boundary):
×
1917
            return on_boundary and x[0] < self.problem.dom.x_range[0] + tol
×
1918

1919
        HH_vel = self.problem.bd.HH_vel
×
1920

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

1925
        # Create a function representing to the inlet velocity
1926
        vel_inlet_func = Function(self.problem.fs.V)
×
1927

1928
        inlet_type = 1
×
1929

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

1936
            vel_vort = np.zeros(np.shape(coords))
×
1937
            vel_inlet = np.zeros(np.shape(coords))
×
1938

1939
            # Specify the vortex radius
1940
            vortRad = 1000
×
1941
            vortRad2 = vortRad**2
×
1942

1943
            # Specify the vortex velocity and calculate its position from the starting point
1944
            vortVel = 1.0
×
1945

1946
            period = 1000.0
×
1947
            xd = period/2 - vortVel*(simTime%period)
×
1948

1949
            fac = 0.1
×
1950
            sep = 650
×
1951
            Tau = 1000
×
1952

1953
            for k, x in enumerate(coords):
×
1954
                if x[0] < self.problem.dom.x_range[0] + tol:
×
1955

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

1960
                    cp = ((x[1] + sep/2)**2 + xd**2)/(4*Tau)
×
1961
                    cn = ((x[1] - sep/2)**2 + xd**2)/(4*Tau)
×
1962

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

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

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

1973
                    if norm > 10.0:
×
1974
                        vel_inlet[k, 0] = vel_inlet[k, 0]/norm*10.0
×
1975
                        vel_inlet[k, 1] = vel_inlet[k, 1]/norm*10.0
×
1976

1977
                    # dx = x - vortPos
1978
                    # dist2 = dx[0]*dx[0] + dx[1]*dx[1]
1979

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

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

1992
        elif inlet_type == 2:
×
1993
            jet_rad = 400
×
1994

1995
            vel_inlet = np.zeros(np.shape(coords))
×
1996

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

2002
                        theta = thetaMax*np.sin(simTime/1000*2*np.pi)
×
2003

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

2010

2011
        # Riffle shuffle the array elements into a 1D vector
2012
        vel_inlet_vector = np.zeros(np.size(vel_inlet))
×
2013

2014
        for k in range(self.problem.dom.dim):
×
2015
            vel_inlet_vector[k::self.problem.dom.dim] = vel_inlet[:, k]
×
2016

2017
        # Assign the function the vector of values
2018
        vel_inlet_func.vector()[:] = vel_inlet_vector
×
2019

2020

2021
        # Update the inlet velocity
2022
        bcu[0] = DirichletBC(self.problem.fs.V, vel_inlet_func, left_wall)
×
2023

2024
        return bcu
×
2025

2026

2027
# ================================================================
2028

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

2034
    Args: 
2035
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
2036
        angles (list): A list of wind inflow directions.
2037
    """ 
2038

2039
    def __init__(self,problem):
1✔
2040
        super(MultiAngleSolver, self).__init__(problem)
×
2041
        if self.params["domain"]["type"] in ["imported"]:
×
2042
            raise ValueError("Cannot use a Multi-Angle Solver with an "+self.params["domain"]["type"]+" domain.")
×
2043
        self.orignal_solve = super(MultiAngleSolver, self).Solve
×
2044
        if self.problem.bd.inflow_angle is None:
×
2045
            self.wind_range = [0, 2.0*np.pi,self.num_wind_angles]
×
2046
        elif isinstance(self.problem.bd.inflow_angle,list):
×
2047
            if len(self.problem.bd.inflow_angle)==3:
×
2048
                self.wind_range = self.problem.bd.inflow_angle
×
2049
            else:
2050
                self.wind_range = [self.problem.bd.inflow_angle[0],self.problem.bd.inflow_angle[1],self.num_wind_angles]
×
2051
        else:
2052
            self.wind_range = [self.problem.bd.inflow_angle,self.problem.bd.inflow_angle+2.0*np.pi,self.num_wind_angles]
×
2053

2054
        self.angles = np.linspace(*self.wind_range,endpoint=self.endpoint)
×
2055
        # self.angles += self.angle_offset
2056

2057
    def Solve(self):
1✔
2058
        for i, theta in enumerate(self.angles):
×
2059
            self.fprint("Performing Solve {:d} of {:d}".format(i+1,len(self.angles)),special="header")
×
2060
            self.fprint("Wind Angle: "+repr(theta))
×
2061
            if i > 0 or not near(theta,self.problem.dom.inflow_angle):
×
2062
                self.problem.dom.inflow_angle = theta
×
2063
                self.ChangeWindAngle(theta)
×
2064
            self.iter_val = theta
×
2065
            self.orignal_solve()
×
2066
            self.fprint("Finished Solve {:d} of {:d}".format(i+1,len(self.angles)),special="footer")
×
2067

2068
class TimeSeriesSolver(SteadySolver):
1✔
2069
    """
2070
    This solver will solve the problem using the steady state solver for every
2071
    angle in angles.
2072

2073
    Args: 
2074
        problem (:meth:`windse.ProblemManager.GenericProblem`): a windse problem object.
2075
        angles (list): A list of wind inflow directions.
2076
    """ 
2077

2078
    def __init__(self,problem):
1✔
2079
        super(TimeSeriesSolver, self).__init__(problem)
×
2080
        if self.params["domain"]["type"] in ["imported"]:
×
2081
            raise ValueError("Cannot use a Multi-Angle Solver with an "+self.params["domain"]["type"]+" domain.")
×
2082
        self.orignal_solve = super(TimeSeriesSolver, self).Solve
×
2083

2084
        raw_data = np.loadtxt(self.velocity_path,comments="#")
×
2085
        self.times = raw_data[:,0]
×
2086
        self.speeds = raw_data[:,1]
×
2087
        self.angles = raw_data[:,2]
×
2088
        self.num_solve = len(self.speeds)
×
2089

2090
    def Solve(self):
1✔
2091
        for i in range(self.num_solve):
×
2092
            time  = self.times[i]
×
2093
            theta = self.angles[i]
×
2094
            speed = self.speeds[i]
×
2095
            self.fprint("Performing Solve {:d} of {:d}".format(i+1,len(self.angles)),special="header")
×
2096
            self.fprint("Time: "+repr(time))
×
2097
            self.fprint("Wind Angle: "+repr(theta))
×
2098
            self.fprint("Wind Speed: "+repr(speed))
×
2099
            if i > 0 or not near(speed,self.problem.bd.HH_vel):
×
2100
                self.ChangeWindSpeed(speed)
×
2101
            if i > 0 or not near(theta,self.problem.dom.inflow_angle):
×
2102
                self.ChangeWindAngle(theta)
×
2103
            self.iter_val = time
×
2104
            self.orignal_solve()
×
2105

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

2108

2109

2110

2111

2112

2113

2114

2115

2116

2117

2118

2119

2120

2121

2122

2123

2124

2125

2126

2127

2128

2129

2130

2131

2132
##### OLD CODE FOR FUTURE REFERENCE ####
2133

2134

2135
    # # def CalculatePowerFunctional_n(self,inflow_angle = 0.0):
2136
    # def CalculatePowerFunctional(self,inflow_angle = 0.0):
2137

2138
    #     # if self.problem.dom.dim == 3:
2139
    #     if self.problem.dom.dim <= 3:
2140
    #         # print("here")
2141
    #         ### Reconstruct Turbine Force ###
2142
    #         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]
2143

2144

2145
    #         W = self.problem.farm.thickness[0]*1.0
2146
    #         R = self.problem.farm.RD[0]/2.0 
2147

2148
    #         T_norm = 2.0*gamma(7.0/6.0)
2149
    #         D_norm = pi*gamma(4.0/3.0)
2150
    #         volNormalization_3D = T_norm*D_norm*W*R**2.0
2151
    #         print("3D Volume: "+repr(volNormalization_3D))
2152

2153
    #         T_norm = 2.0*gamma(7.0/6.0)
2154
    #         D_norm = 2.0*gamma(7.0/6.0)
2155
    #         volNormalization_2D = T_norm*D_norm*W*R
2156
    #         print("3D Volume: "+repr(volNormalization_2D))
2157

2158

2159
    #         if self.problem.dom.dim == 2:
2160
    #             dim_scale = volNormalization_3D/volNormalization_2D
2161
    #             print(dim_scale)
2162
    #             dim_scale = pi*R/2
2163
    #             print(dim_scale)
2164
    #             dim_scale = 2*R*R*volNormalization_2D/volNormalization_3D
2165
    #             print(dim_scale)
2166
    #         else: 
2167
    #             dim_scale = 1.0
2168

2169
    #         ### Calculate Power ###
2170
    #         J = dot(tf*dim_scale,self.u_next)*dx
2171

2172
    #         ### Save Individual Powers ###
2173
    #         if self.save_power:
2174
    #             J_list=np.zeros(self.problem.farm.numturbs+1)
2175
    #             J_list[-1]=assemble(J,**self.extra_kwarg)
2176

2177
    #             if self.problem.farm.actuator_disks_list is not None:
2178
    #                 for i in range(self.problem.farm.numturbs):
2179
    #                     yaw = self.problem.farm.myaw[i]+inflow_angle
2180
    #                     tf1 = self.problem.farm.actuator_disks_list[i] * cos(yaw)**2
2181
    #                     tf2 = self.problem.farm.actuator_disks_list[i] * sin(yaw)**2
2182
    #                     tf3 = self.problem.farm.actuator_disks_list[i] * 2.0 * cos(yaw) * sin(yaw)
2183
    #                     tf = tf1*self.u_next[0]**2+tf2*self.u_next[1]**2+tf3*self.u_next[0]*self.u_next[1]
2184
    #                     J_list[i] = assemble(dot(tf*dim_scale,self.u_next)*dx,**self.extra_kwarg)
2185

2186
    #             for j in J_list:
2187
    #                 print(j)
2188
    #             # exit()
2189
    #     # else:
2190
    #     #     J=0
2191
    #     #     J_list=np.zeros(self.problem.farm.numturbs+1)
2192
    #     #     for i in range(self.problem.farm.numturbs):
2193

2194
    #     #         ### Set some Values ###
2195
    #     #         yaw = self.problem.farm.myaw[i]+inflow_angle
2196
    #     #         W = self.problem.farm.W[i]
2197
    #     #         R = self.problem.farm.RD[i]/2.0 
2198
    #     #         A = pi*R**2.0
2199
    #     #         a = self.problem.farm.ma[i]
2200
    #     #         C_tprime = 4*a/(1-a)
2201
    #     #         C_pprime = 0.45/(1-a)**3
2202

2203
    #     #         ### Define Integral Constants ###
2204
    #     #         T_norm = 2.0*gamma(7.0/6.0)
2205
    #     #         D_norm = 2.0*gamma(7.0/6.0)
2206
    #     #         h1 = float(hyper([],[1/3, 2/3, 5/6, 7/6],-(np.pi**6/46656)))
2207
    #     #         h2 = float(hyper([],[2/3, 7/6, 4/3, 3/2],-(np.pi**6/46656)))
2208
    #     #         h3 = float(hyper([],[4/3, 3/2, 5/3, 11/6],-(np.pi**6/46656)))
2209
    #     #         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)
2210

2211
    #     #         ### Reconstruct Turbine Force ###
2212
    #     #         tf1 = self.problem.farm.actuator_disks_list[i] * cos(yaw)**2
2213
    #     #         tf2 = self.problem.farm.actuator_disks_list[i] * sin(yaw)**2
2214
    #     #         tf3 = self.problem.farm.actuator_disks_list[i] * 2.0 * cos(yaw) * sin(yaw)
2215
    #     #         tf = tf1*self.u_next[0]**2+tf2*self.u_next[1]**2+tf3*self.u_next[0]*self.u_next[1]
2216
    #     #         tf = tf*T_norm*D_norm*W*R
2217

2218
    #     #         ### Calculate Volume of the Domain ###
2219
    #     #         unit = Function(self.problem.fs.Q)
2220
    #     #         unit.vector()[:] = 1.0
2221
    #     #         vol = assemble(unit*dx)
2222

2223
    #     #         ### Calculate Integral of Actuator Disk ###
2224
    #     #         if self.problem.farm.force == "constant":
2225
    #     #             Va = C_tprime*T_norm*D_norm*W*R*R
2226
    #     #         elif self.problem.farm.force == "sine":
2227
    #     #             Va = C_tprime*T_norm*SD_norm*W*R*R
2228

2229
    #     #         ### Calculate Power ###
2230
    #     #         # J_current = 0.5*A*C_pprime*(assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2231
    #     #         # J_current = 0.5*A*C_pprime/vol*(dot(tf,self.u_next)/Va)**3*dx
2232
    #     #         J_current = 0.5*A*C_pprime/vol*(dot(tf,self.u_next)/Va)*dx
2233
    #     #         # J_current = 0.5*A*C_pprime/vol*(unit/Va)**3*dx
2234
    #     #         # J_current = unit*0.5*A*C_pprime/vol*(1/Va)**3*dx
2235
    #     #         # J_current = unit*Va*dx
2236
    #     #         J += J_current
2237

2238
    #     #         if self.save_power:
2239
    #     #             J_list[i] = assemble(J_current)
2240

2241
    #     #     if self.save_power:
2242
    #     #         J_list[-1]=np.sum(J_list[:-1])
2243
    #     #         print()
2244
    #     #         for j in J_list:
2245
    #     #             print(j)
2246
    #     #         # exit()
2247

2248
    #         folder_string = self.params.folder+"/data/"
2249
    #         if not os.path.exists(folder_string): os.makedirs(folder_string)
2250

2251
    #         if self.J_saved:
2252
    #             f = open(folder_string+"power_data.txt",'ab')
2253
    #         else:
2254
    #             f = open(folder_string+"power_data.txt",'wb')
2255
    #             self.J_saved = True
2256

2257
    #         np.savetxt(f,[J_list])
2258
    #         f.close()
2259

2260
    #     return J
2261
        
2262
    # # def CalculatePowerFunctional_o(self,delta_yaw = 0.0):
2263
    # # # def CalculatePowerFunctional(self,delta_yaw = 0.0):
2264
    # #     self.fprint("Computing Power Functional")
2265

2266
    # #     x=SpatialCoordinate(self.problem.dom.mesh)
2267
    # #     J=0.
2268
    # #     J_list=np.zeros(self.problem.farm.numturbs+1)
2269
    # #     for i in range(self.problem.farm.numturbs):
2270

2271
    # #         mx = self.problem.farm.mx[i]
2272
    # #         my = self.problem.farm.my[i]
2273
    # #         mz = self.problem.farm.mz[i]
2274
    # #         x0 = [mx,my,mz]
2275
    # #         W = self.problem.farm.thickness[i]*1.0
2276
    # #         R = self.problem.farm.RD[i]/2.0 
2277
    # #         ma = self.problem.farm.ma[i]
2278
    # #         yaw = self.problem.farm.myaw[i]+delta_yaw
2279
    # #         u = self.u_next
2280
    # #         A = pi*R**2.0
2281
    # #         C_tprime = 4*ma/(1-ma)
2282
    # #         C_pprime = 0.45/(1-ma)**3
2283
            
2284
    # #         ### Rotate and Shift the Turbine ###
2285
    # #         xs = self.problem.farm.YawTurbine(x,x0,yaw)
2286
    # #         u_d = u[0]*cos(yaw) + u[1]*sin(yaw)
2287

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

2291
    # #         if self.problem.dom.dim == 3:
2292
    # #             # WTGbase = Expression(("cos(yaw)","sin(yaw)","0.0"),yaw=yaw,degree=1)
2293
    # #             WTGbase = as_vector((cos(yaw),sin(yaw),0.0))
2294

2295
    # #             ### Create the function that represents the Disk of the turbine
2296
    # #             r = sqrt(xs[1]**2.0+xs[2]**2.0)/R
2297
    # #             D = exp(-pow(r,6.0))
2298
    # #             # D = exp(-pow((pow((xs[1]/R),2)+pow((xs[2]/R),2)),6.0))
2299

2300
    # #             volNormalization = assemble(T*D*dx)
2301
    # #             print(volNormalization)
2302
    # #             T_norm = 2.0*gamma(7.0/6.0)
2303
    # #             D_norm = pi*gamma(4.0/3.0)
2304
    # #             volNormalization = T_norm*D_norm*W*R**2.0
2305
    # #             print(volNormalization)
2306

2307
    # #             ### Create the function that represents the force ###
2308
    # #             if self.problem.farm.force == "constant":
2309
    # #                 F = 0.5*A*C_tprime
2310
    # #             elif self.problem.farm.force == "sine":
2311
    # #                 # r = sqrt(xs[1]**2.0+xs[2]**2)
2312
    # #                 F = 0.5*A*C_tprime*(r*sin(pi*r)+0.5)/(.81831)
2313

2314
    # #             J_current = dot(F*T*D*WTGbase*u_d**2,u)*dx
2315
    # #             J += J_current
2316
    # #             if self.save_power:
2317
    # #                 J_list[i] = assemble(J_current)
2318

2319
    # #         else:
2320
    # #             # WTGbase = Expression(("cos(yaw)","sin(yaw)"),yaw=yaw,degree=1)
2321
    # #             WTGbase = as_vector((cos(yaw),sin(yaw)))
2322

2323
    # #             ### Create the function that represents the Disk of the turbine
2324
    # #             r = sqrt(xs[1]**2.0+xs[2]**2.0)/R
2325
    # #             D = exp(-pow(r,6.0))
2326

2327

2328
    # #             # T_norm = 2.0*gamma(7.0/6.0)
2329
    # #             # D_norm = 2.0*gamma(7.0/6.0)
2330
    # #             # h1 = float(hyper([],[1/3, 2/3, 5/6, 7/6],-(np.pi**6/46656)))
2331
    # #             # h2 = float(hyper([],[2/3, 7/6, 4/3, 3/2],-(np.pi**6/46656)))
2332
    # #             # h3 = float(hyper([],[4/3, 3/2, 5/3, 11/6],-(np.pi**6/46656)))
2333
    # #             # 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)
2334
    # #             # volNormalization = T_norm*SD_norm*W*R**(self.problem.dom.dim)
2335
    # #             volNormalization = assemble(T*D*dx)
2336

2337
    # #             T_norm = 2.0*gamma(7.0/6.0)
2338
    # #             D_norm = pi*gamma(4.0/3.0)
2339
    # #             volNormalization_3D = T_norm*D_norm*W*R**2.0
2340
    # #             # print(volNormalization_3D)
2341

2342

2343
    # #             T_norm = 2.0*gamma(7.0/6.0)
2344
    # #             D_norm = 2.0*gamma(7.0/6.0)
2345
    # #             volNormalization_2D = T_norm*D_norm*W*R
2346
    # #             # print(volNormalization_2D)
2347

2348

2349
    # #             print(volNormalization,volNormalization_2D,volNormalization_3D,2*volNormalization_2D/volNormalization_3D)
2350

2351
    # #             ### Create the function that represents the force ###
2352
    # #             if self.problem.farm.force == "constant":
2353
    # #                 F = 0.5*self.problem.farm.RD[i]*C_tprime    
2354
    # #             elif self.problem.farm.force == "sine":
2355
    # #                 F = 0.5*self.problem.farm.RD[i]*C_tprime*(r*sin(pi*r)+0.5)/(.81831)
2356

2357
    # #             # V  = assemble(F*T*D*dx)
2358
    # #             # Va = float(C_tprime)*T_norm*SD_norm*W*R*R
2359
    # #             # print(V,Va,V/Va,V/(W*R*R),Va/(W*R*R))
2360

2361
    # #             J_current = assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)
2362
    # #             # J_current = (assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)
2363
    # #             # J_current = 0.5*A*C_pprime*(assemble(dot(F*T*D*WTGbase*u_d**2,u)*dx)/assemble(F*T*D*dx))
2364
    # #             # print(float(J_current))
2365
    # #             # J_current_old = 0.5*A*C_pprime*(assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2366
    # #             # J_current = 0.5*A*C_pprime*(assemble(T*D*u*dx)/assemble(T*D*dx))**3
2367

2368
    # #             # J_3D = [1692363.167076575,801778.7751333286,545135.3528982735]
2369
    # #             # J_3D = [767698.3159772983,420644.4831798226,291267.477222329]
2370
    # #             # print(float(J_current_old),float(J_current_old/J_current),float(J_3D[i]/J_current))
2371
    # #             # J_current = (assemble(0.5*A*C_pprime*F*T*D*u_d*dx)/assemble(F*T*D*dx))
2372

2373

2374

2375

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

2382

2383

2384
 
2385

2386

2387

2388

2389

2390

2391

2392

2393

2394

2395
    # #             # J_current = 0.5*A*C_pprime*(1/assemble(F*T*D*dx))**3
2396
    # #             J += J_current
2397
    # #             if self.save_power:
2398
    # #                 # J_list[i] = (assemble(F*T*D*u_d*dx)/assemble(F*T*D*dx))**3
2399
    # #                 J_list[i] = J_current
2400

2401
    # #     if self.save_power:
2402
    # #         J_list[-1]=np.sum(J_list[:-1])
2403
    # #         print()
2404
    # #         for j in J_list:
2405
    # #             print(j)
2406
    # #         exit()
2407

2408
    # #         folder_string = self.params.folder+"/data/"
2409
    # #         if not os.path.exists(folder_string): os.makedirs(folder_string)
2410

2411
    # #         if self.J_saved:
2412
    # #             f = open(folder_string+"power_data.txt",'ab')
2413
    # #         else:
2414
    # #             f = open(folder_string+"power_data.txt",'wb')
2415
    # #             self.J_saved = True
2416

2417
    # #         np.savetxt(f,[J_list])
2418
    # #         f.close()
2419

2420
    # #     return J
2421

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