forked from mdolab/pygeo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
DVConstraints.py
5404 lines (4541 loc) · 212 KB
/
DVConstraints.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# ======================================================================
# Imports
# ======================================================================
from __future__ import print_function
import numpy,copy
from pygeo import geo_utils, pyGeo
from pyspline import pySpline
from mpi4py import MPI
from scipy.sparse import csr_matrix
try:
from collections import OrderedDict
except ImportError:
try:
from ordereddict import OrderedDict
except ImportError:
print("Could not find any OrderedDict class. For 2.6 and earlier, "
"use:\n pip install ordereddict")
class Error(Exception):
"""
Format the error message in a box to make it clear this
was a expliclty raised exception.
"""
def __init__(self, message):
msg = '\n+'+'-'*78+'+'+'\n' + '| DVCon Error: '
i = 14
for word in message.split():
if len(word) + i + 1 > 78: # Finish line and start new one
msg += ' '*(78-i)+'|\n| ' + word + ' '
i = 1 + len(word)+1
else:
msg += word + ' '
i += len(word)+1
msg += ' '*(78-i) + '|\n' + '+'+'-'*78+'+'+'\n'
print(msg)
Exception.__init__(self)
class Warning(object):
"""
Format a warning message
"""
def __init__(self, message):
msg = '\n+'+'-'*78+'+'+'\n' + '| DVConstraints Warning: '
i = 24
for word in message.split():
if len(word) + i + 1 > 78: # Finish line and start new one
msg += ' '*(78-i)+'|\n| ' + word + ' '
i = 1 + len(word)+1
else:
msg += word + ' '
i += len(word)+1
msg += ' '*(78-i) + '|\n' + '+'+'-'*78+'+'+'\n'
print(msg)
class GeometricConstraint(object):
"""
This is a generic base class for all of the geometric constraints.
"""
def __init__(self,name, nCon, lower, upper, scale, DVGeo, addToPyOpt):
"""
General init function. Every constraint has these functions
"""
self.name = name
self.nCon = nCon
self.lower = lower
self.upper = upper
self.scale = scale
self.DVGeo = DVGeo
self.addToPyOpt = addToPyOpt
return
def setDesignVars(self,x):
"""
take in the design var vector from pyopt and set the variables for this constraint
This function is constraint specific, so the baseclass doesn't implement anything.
"""
pass
def evalFunctions(self, funcs, config):
"""
Evaluate the functions this object has and place in the funcs dictionary.
This function is constraint specific, so the baseclass doesn't implement anything.
Parameters
----------
funcs : dict
Dictionary to place function values
"""
pass
def evalFunctionsSens(self, funcsSens, config):
"""
Evaluate the sensitivity of the functions this object has and
place in the funcsSens dictionary
This function is constraint specific, so the baseclass doesn't implement anything.
Parameters
----------
funcsSens : dict
Dictionary to place function values
"""
pass
def getVarNames(self):
"""
return the var names relevant to this constraint. By default, this is the DVGeo
variables, but some constraints may extend this to include other variables.
"""
return self.DVGeo.getVarNames()
def addConstraintsPyOpt(self, optProb):
"""
Add the constraints to pyOpt, if the flag is set
"""
if self.addToPyOpt:
optProb.addConGroup(self.name, self.nCon, lower=self.lower,
upper=self.upper, scale=self.scale,
wrt=self.getVarNames())
def addVariablesPyOpt(self, optProb):
"""
Add the variables to pyOpt, if the flag is set
"""
# if self.addToPyOpt:
# optProb.addVarGroup(self.name, self.nCon, lower=self.lower,
# upper=self.upper, scale=self.scale,
# wrt=self.getVarNames())
def writeTecplot(self, handle):
"""
Write the visualization of this set of thickness constraints
to the open file handle
This function is constraint specific, so the baseclass doesn't implement anything.
"""
pass
class DVConstraints(object):
"""DVConstraints provides a convenient way of defining geometric
constraints for WINGS. This can be very useful for a constrained
aerodynamic or aerostructural optimization. Three types of
constraints are supported:
1. Thickness 'tooth-pick' constraints: Thickness constraints are
enforced at specific locations. A relative or absolute
minimum/maximum thickness can be specified. Two variants are
supplied: a '2d' variant for thickness constraints over an area
such as a spar box :func:`addThicknessConstraints2D` and a '1d'
variant for thickness constraints along a (poly) line
:func:`addThicknessConstraints1D`.
2. Volume constraint: This computes and enforces a volume constraint
over the specified domain. The input is identical to the '2d'
thickness constraints. See :func:`addVolumeConstraint`.
3. LE/TE Constraints: These geometric constraints are required when
using FFD volumes with shape variables. The leading and trailing
edges must be fixed wrt the shape variables so these enforce that the
coefficients on the leading edge can only move in equal and opposite
directions
4. Fixed Location Constraints: These constraints allow you to specify
certain location in the FFD that can not move.
5. Gear Post Constraint: This is a very highly specialized
constraint used to ensure there is sufficient vertical space to
install wing-mounted landing gear and that it is correctly positioned
relative to the wing. See the class definition for more information.
Analytic sensitivity information is computed for all functions and a
facility for adding the constraints automatically to a pyOptSparse
optimization problem is also provided.
Parameters
----------
name: str
A name for this object. Used to distiguish between DVCon objects
if multiple DVConstraint objects are used in an optimization.
"""
def __init__(self,name='DVCon1'):
"""
Create a (empty) DVconstrains object. Specific types of
constraints will added individually
"""
self.name = name
self.constraints = OrderedDict()
self.linearCon = OrderedDict()
self.DVGeo = None
# Data for the discrete surface
self.p0 = None
self.v1 = None
self.v2 = None
def setSurface(self, surf):
"""
Set the surface DVConstraints will use to perform projections.
Parameters
----------
surf : pyGeo object or list
This is the surface representation to use for
projections. If available, a pyGeo surface object can be
used OR a triagnulaed surface in the form [p0, v1, v2] can
be used. This triangulated surface form can be supplied
form pyADflow or from pyTrian.
Examples
--------
>>> CFDsolver = ADFLOW(comm=comm, options=aeroOptions)
>>> surf = CFDsolver.getTriangulatedMeshSurface()
>>> DVCon.setSurface(surf)
>>> # Or using a pyGeo surface object:
>>> surf = pyGeo('iges',fileName='wing.igs')
>>> DVCon.setSurface(surf)
"""
if type(surf) == list:
# Data from ADflow
self.p0 = numpy.array(surf[0])
self.v1 = numpy.array(surf[1])
self.v2 = numpy.array(surf[2])
elif isinstance(surf, str):
# Load the surf as a plot3d file
self.p0, self.v1, self.v2 = self._readPlot3DSurfFile(surf)
else: # Assume it's a pyGeo surface
self._generateDiscreteSurface(surf)
def setDVGeo(self, DVGeo):
"""
Set the DVGeometry object that will manipulate this object.
Note that DVConstraints doesn't **strictly** need a DVGeometry
object set, but if optimization is desired it is required.
Parameters
----------
dvGeo : A DVGeometry object.
Object responsible for manipulating the constraints that
this object is responsible for.
Examples
--------
>>> dvCon.setDVGeo(DVGeo)
"""
self.DVGeo = DVGeo
def addConstraintsPyOpt(self, optProb):
"""
Add all constraints to the optProb object. Only constraints
the that have the addToPyOpt flags are actually added.
Parameters
----------
optProb : pyOpt_optimization object
Optimization description to which the constraints are added
Examples
--------
>>> DVCon.addConstraintsPyOpt(optProb)
"""
# loop over the generated constraint objects and add the necessary
# constraints to pyopt
for conTypeKey in self.constraints:
constraint = self.constraints[conTypeKey]
for key in constraint:
constraint[key].addConstraintsPyOpt(optProb)
# add the linear constraints separately, since they are treated a bit differently
for key in self.linearCon:
self.linearCon[key].addConstraintsPyOpt(optProb)
def addVariablesPyOpt(self, optProb):
"""
Add all constraint variables to the optProb object.
Parameters
----------
optProb : pyOpt_optimization object
Optimization description to which the constraints are added
Examples
--------
>>> DVCon.addVariablesPyOpt(optProb)
"""
# loop over the generated constraint objects and add the necessary
# variables to pyopt
for conTypeKey in self.constraints:
constraint = self.constraints[conTypeKey]
for key in constraint:
constraint[key].addVariablesPyOpt(optProb)
# linear contraints are ignored because at the moment there are no linear
# constraints that have independent variables
def setDesignVars(self, dvDict):
"""
Standard routine for setting design variables from a design
variable dictionary.
Parameters
----------
dvDict : dict
Dictionary of design variables. The keys of the dictionary
must correspond to the design variable names. Any
additional keys in the dfvdictionary are simply ignored.
"""
# loop over the generated constraint objects and add the necessary
# variables to pyopt
for conTypeKey in self.constraints:
constraint = self.constraints[conTypeKey]
for key in constraint:
constraint[key].setDesignVars(dvDict)
# linear contraints are ignored because at the moment there are no linear
# constraints that have independent variables
def evalFunctions(self, funcs, includeLinear=False, config=None):
"""
Evaluate all the 'functions' that this object has. Of course,
these functions are usually just the desired constraint
values. These values will be set directly into the funcs
dictionary.
Parameters
----------
funcs : dict
Dictionary into which the function values are placed.
includeLeTe : bool
Flag to include Leading/Trailing edge
constraints. Normally this can be false since pyOptSparse
does not need linear constraints to be returned.
"""
# loop over the generated constraints and evaluate their function values
for conTypeKey in self.constraints:
constraint = self.constraints[conTypeKey]
for key in constraint:
constraint[key].evalFunctions(funcs, config)
if includeLinear:
for key in self.linearCon:
self.linearCon[key].evalFunctions(funcs)
def evalFunctionsSens(self, funcsSens, includeLinear=False, config=None):
"""
Evaluate the derivative of all the 'funcitons' that this
object has. These functions are just the constraint values.
Thse values will be set directly in the funcSens dictionary.
Parameters
----------
funcSens : dict
Dictionary into which the sensitivities are added.
includeLeTe : bool
Flag to include Leading/Trailing edge
constraints. Normally this can be false since pyOptSparse
does not need linear constraints to be returned.
"""
# loop over the generated constraints and evaluate their function values
for conTypeKey in self.constraints:
constraint = self.constraints[conTypeKey]
for key in constraint:
constraint[key].evalFunctionsSens(funcsSens, config)
if includeLinear:
for key in self.linearCon:
self.linearCon[key].evalFunctionsSens(funcsSens)
def writeTecplot(self, fileName):
"""
This function writes a visualization file for constraints. All
currently added constraints are written to a tecplot. This is
useful for publication purposes as well as determine if the
constraints are *actually* what the user expects them to be.
Parameters
----------
fileName : str
File name for tecplot file. Should have a .dat extension or a
.dat extension will be added automatically.
"""
f = open(fileName, 'w')
f.write("TITLE = \"DVConstraints Data\"\n")
f.write("VARIABLES = \"CoordinateX\" \"CoordinateY\" \"CoordinateZ\"\n")
# loop over the constraints and add their data to the tecplot file
for conTypeKey in self.constraints:
constraint = self.constraints[conTypeKey]
for key in constraint:
constraint[key].writeTecplot(f)
for key in self.linearCon:
self.linearCon[key].writeTecplot(f)
f.close()
def writeSurfaceTecplot(self,fileName):
"""
Write the triangulated surface mesh used in the constraint object
to a tecplot file for visualization.
Parameters
----------
fileName : str
File name for tecplot file. Should have a .dat extension.
"""
f = open(fileName, 'w')
f.write("TITLE = \"DVConstraints Surface Mesh\"\n")
f.write("VARIABLES = \"CoordinateX\" \"CoordinateY\" \"CoordinateZ\"\n")
f.write('Zone T=%s\n'%('surf'))
f.write('Nodes = %d, Elements = %d ZONETYPE=FETRIANGLE\n'% (
len(self.p0)*3, len(self.p0)))
f.write('DATAPACKING=POINT\n')
for i in range(len(self.p0)):
points = []
points.append(self.p0[i])
points.append(self.p0[i]+self.v1[i])
points.append(self.p0[i]+self.v2[i])
for i in range(len(points)):
f.write('%f %f %f\n'% (points[i][0], points[i][1],points[i][2]))
for i in range(len(self.p0)):
f.write('%d %d %d\n'% (3*i+1, 3*i+2,3*i+3))
f.close()
def addThicknessConstraints2D(self, leList, teList, nSpan, nChord,
lower=1.0, upper=3.0, scaled=True, scale=1.0,
name=None, addToPyOpt=True):
"""
Add a set of thickness constraints that span a logically a
two-dimensional region. A little ASCII art can help here
.. code-block:: text
Planform view of the wing: The '+' are the (three dimensional)
points that are supplied in leList and teList. The 'o' is described below.
Physical extent of wing
\
__________________________\_________
| |
+-----------+--o-----------+ |
| / \ |
| leList teList \ |
| \ \ |
+------------------------------+ |
| |
|__________________________________/
Things to consider:
* The region given by leList and teList must lie completely
inside the wing
* The number of points in leList and teList do not need to be
the same length.
* The leading and trailing edges are approximated using
2-order splines (line segments) and nSpan points are
interpolated in a linear fashion. Note that the thickness
constraint may not correspond **EXACT** to intermediate
locations in leList and teList. For example, in the example
above, with leList=3 and nSpan=3, the three thickness
constraints on the leading edge of the 2D domain would be at
the left and right boundaries, and at the point denoted by
'o' which is equidistance between the root and tip.
* If a curved leading or trailing edge domain is desired,
simply pass in lists for leList and teList with a sufficient
number of points that can be approximated with straight line
segments.
* The two-dimensional data is projected onto the surface along
the normals of the ruled surface formed by leList and teList
* Normally, leList and teList are in given such that the the two curves
entirely lie in a plane. This ensure that the projection vectors
are always exactly normal to this plane.
* If the surface formed by leList and teList is NOT precisely
normal, issues can arise near the end of an opensurface (ie
root of a single wing) which can result in failing
intersections.
Parameters
----------
leList : list or array
A list or array of points (size should be (Nx3) where N is
at least 2) defining the 'leading edge' or the start of the
domain
teList : list or array
Same as leList but for the trailing edge.
nSpan : int
The number of thickness constraints to be (linear)
interpolated *along* the leading and trailing edges
nChord : int
The number of thickness constraints to be (linearly)
interpolated between the leading and trailing edges
lower : float or array of size (nSpan x nChord)
The lower bound for the constraint. A single float will
apply the same bounds to all constraints, while the array
option will use different bounds for each constraint.
upper : float or array of size (nSpan x nChord)
The upper bound for the constraint. A single float will
apply the same bounds to all constraints, while the array
option will use different bounds for each constraint.
scaled : bool
Flag specifying whether or not the constraint is to be
implemented in a scaled fashion or not.
* scaled=True: The initial length of each thickness
constraint is defined to be 1.0. In this case, the lower
and upper bounds are given in multiple of the initial
length. lower=0.85, upper=1.15, would allow for 15%
change in each direction from the original length. For
aerodynamic shape optimizations, this option is used
most often.
* scaled=False: No scaling is applied and the phyical lengths
must be specified for the lower and upper bounds.
scale : float or array of size (nSpan x nChord)
This is the optimization scaling of the
constraint. Typically this parameter will not need to be
changed. If the thickness constraints are scaled, this
already results in well-scaled constraint values, and
scale can be left at 1.0. If scaled=False, it may changed
to a more suitable value of the resulting physical
thickness have magnitudes vastly different than O(1).
name : str
Normally this does not need to be set. Only use this if
you have multiple DVCon objects and the constriant names
need to be distinguished **or** the values are to be used
in a subsequent computation.
addToPyOpt : bool
Normally this should be left at the default of True. If
the values need to be processed (modified) BEFORE they are
given to the optimizer, set this flag to False.
Examples
--------
>>> # Take unique square in x-z plane and and 10 along z-direction (spanWise)
>>> # and the along x-direction (chordWise)
>>> leList = [[0, 0, 0], [0, 0, 1]]
>>> teList = [[1, 0, 0], [0, 0, 1]]
>>> DVCon.addThicknessConstraints2D(leList, teList, 10, 3,
lower=1.0, scaled=True)
"""
self._checkDVGeo()
upper = self._convertTo2D(upper, nSpan, nChord).flatten()
lower = self._convertTo2D(lower, nSpan, nChord).flatten()
scale = self._convertTo2D(scale, nSpan, nChord).flatten()
coords = self._generateIntersections(leList, teList, nSpan, nChord)
# Create the thickness constraint object:
coords = coords.reshape((nSpan*nChord*2, 3))
typeName = 'thickCon'
if not typeName in self.constraints:
self.constraints[typeName] = OrderedDict()
# Create a name
if name is None:
conName = '%s_thickness_constraints_%d'%(self.name, len(self.constraints[typeName]))
else:
conName = name
self.constraints[typeName][conName] = ThicknessConstraint(
conName, coords, lower, upper, scaled, scale, self.DVGeo,
addToPyOpt)
def addThicknessConstraints1D(self, ptList, nCon, axis,
lower=1.0, upper=3.0, scaled=True,
scale=1.0, name=None,
addToPyOpt=True):
"""
Add a set of thickness constraints oriented along a poly-line.
See below for a schematic
.. code-block:: text
Planform view of the wing: The '+' are the (three dimensional)
points that are supplied in ptList:
Physical extent of wing
\
__________________________\_________
| + |
| -/ |
| / |
| +-------+-----+ |
| 4-points defining |
| poly-line |
| |
|__________________________________/
Parameters
----------
ptList : list or array of size (N x 3) where N >=2
The list of points forming a poly-line along which the
thickness constraints will be added.
nCon : int
The number of thickness constraints to add
axis : list or array of length 3
The direction along which the projections will occur.
Typically this will be y or z axis ([0,1,0] or [0,0,1])
lower : float or array of size nCon
The lower bound for the constraint. A single float will
apply the same bounds to all constraints, while the array
option will use different bounds for each constraint.
upper : float or array of size nCon
The upper bound for the constraint. A single float will
apply the same bounds to all constraints, while the array
option will use different bounds for each constraint.
scaled : bool
Flag specifying whether or not the constraint is to be
implemented in a scaled fashion or not.
* scaled=True: The initial length of each thickness
constraint is defined to be 1.0. In this case, the lower
and upper bounds are given in multiple of the initial
length. lower=0.85, upper=1.15, would allow for 15%
change in each direction from the original length. For
aerodynamic shape optimizations, this option is used
most often.
* scaled=False: No scaling is applied and the phyical lengths
must be specified for the lower and upper bounds.
scale : float or array of size nCon
This is the optimization scaling of the
constraint. Typically this parameter will not need to be
changed. If the thickness constraints are scaled, this
already results in well-scaled constraint values, and
scale can be left at 1.0. If scaled=False, it may changed
to a more suitable value of the resulting physical
thickness have magnitudes vastly different than O(1).
name : str
Normally this does not need to be set. Only use this if
you have multiple DVCon objects and the constriant names
need to be distinguished **or** you are using this set of
thickness constraints for something other than a direct
constraint in pyOptSparse.
addToPyOpt : bool
Normally this should be left at the default of True. If
the values need to be processed (modified) BEFORE they are
given to the optimizer, set this flag to False.
"""
self._checkDVGeo()
# Create mesh of itersections
constr_line = pySpline.Curve(X=ptList, k=2)
s = numpy.linspace(0, 1, nCon)
X = constr_line(s)
coords = numpy.zeros((nCon, 2, 3))
# Project all the points
for i in range(nCon):
# Project actual node:
up, down, fail = geo_utils.projectNode(
X[i], axis, self.p0, self.v1, self.v2)
if fail > 0:
raise Error("There was an error projecting a node "
"at (%f, %f, %f) with normal (%f, %f, %f)."% (
X[i, 0], X[i, 1], X[i, 2], axis[0], axis[1], axis[2]))
coords[i, 0] = up
coords[i, 1] = down
# Create the thickness constraint object:
coords = coords.reshape((nCon*2, 3))
typeName = 'thickCon'
if not typeName in self.constraints:
self.constraints[typeName] = OrderedDict()
if name is None:
conName = '%s_thickness_constraints_%d'%(self.name,len(self.constraints[typeName]))
else:
conName = name
self.constraints[typeName][conName] = ThicknessConstraint(
conName, coords, lower, upper, scaled, scale, self.DVGeo,
addToPyOpt)
def addLERadiusConstraints(self, leList, nSpan, axis, chordDir,
lower=1.0, upper=3.0, scaled=True,
scale=1.0, name=None, addToPyOpt=True):
"""
Add a set of leading edge radius constraints. The constraint is set up
similar to the 1D thickness or thickness-to-chord constraints. The user
provides a polyline near the leading edge and specifies how many
locations should be sampled along the polyline. The sampled points are
then projected to the upper and lower surface along the provided axis.
A third projection is made to the leading edge along chordDir. We then
compute the radius of the circle circumscribed by these three points.
In order for this radius calculation to be a reasonable approximation
for the actual leading edge radius, it is critical that the polyline be
drawn very close to the leading edge (less than 0.5% chord). We include
a check to make sure that the points fall on the same hemisphere of the
circumscribed circle, however we recommend that the user export the
Tecplot view of the constraint using the writeTecplot function to verify
that the circles do coincide with the leading edge radius.
See below for a schematic
.. code-block:: text
Planform view of the wing: The '+' are the (three dimensional)
points that are supplied in leList:
Physical extent of wing
\
__________________________\_________
| +-------+-----+-------+-----+ |
| 5 points defining |
| poly-line |
| |
| |
| |
|__________________________________/
Parameters
----------
leList : list or array of size (N x 3) where N >=2
The list of points forming a poly-line along which the
thickness constraints will be added.
nSpan : int
The number of thickness constraints to add
axis : list or array of length 3
The direction along which the up-down projections will occur.
Typically this will be y or z axis ([0,1,0] or [0,0,1])
chordDir : list or array or length 3
The vector pointing from the leList to the leading edge. This will
typically be the negative xaxis ([-1,0,0]). The magnitude of the
vector doesn't matter, but the direction does.
lower : float or array of size nSpan
The lower bound for the constraint. A single float will
apply the same bounds to all constraints, while the array
option will use different bounds for each constraint.
upper : float or array of size nSpan
The upper bound for the constraint. A single float will
apply the same bounds to all constraints, while the array
option will use different bounds for each constraint.
scaled : bool
Flag specifying whether or not the constraint is to be
implemented in a scaled fashion or not.
* scaled=True: The initial radius of each constraint is defined to
be 1.0. In this case, the lower and upper bounds are given in
multiples of the initial radius. lower=0.85, upper=1.15, would
allow for 15% change in each direction from the original radius.
* scaled=False: No scaling is applied and the phyical radii
must be specified for the lower and upper bounds.
scale : float or array of size nSpan
This is the optimization scaling of the constraint. Typically this
parameter will not need to be changed. If the radius constraints are
scaled, this already results in well-scaled constraint values, and
scale can be left at 1.0. If scaled=False, it may be changed to a
more suitable value if the resulting physical thickness have
magnitudes vastly different than O(1).
name : str
Normally this does not need to be set. Only use this if you have
multiple DVCon objects and the constriant names need to be
distinguished **or** you are using this set of thickness constraints
for something other than a direct constraint in pyOptSparse.
addToPyOpt : bool
Normally this should be left at the default of True. If
the values need to be processed (modified) BEFORE they are
given to the optimizer, set this flag to False.
"""
self._checkDVGeo()
# Create mesh of itersections
constr_line = pySpline.Curve(X=leList, k=2)
s = numpy.linspace(0, 1, nSpan)
X = constr_line(s)
coords = numpy.zeros((nSpan, 3, 3))
# Project all the points
for i in range(nSpan):
# Project actual node:
up, down, fail = geo_utils.projectNode(
X[i], axis, self.p0, self.v1, self.v2)
if fail > 0:
raise Error("There was an error projecting a node "
"at (%f, %f, %f) with normal (%f, %f, %f)."% (
X[i, 0], X[i, 1], X[i, 2],
axis[0], axis[1], axis[2]))
coords[i, 0] = up
coords[i, 1] = down
# Calculate mid-points
midPts = (coords[:,0,:] + coords[:,1,:]) / 2.0
# Project to get leading edge point
lePts = numpy.zeros((nSpan, 3))
chordDir = numpy.array(chordDir, dtype='d').flatten()
chordDir /= numpy.linalg.norm(chordDir)
for i in range(nSpan):
# Project actual node:
up, down, fail = geo_utils.projectNode(
X[i], chordDir, self.p0, self.v1, self.v2)
if fail > 0:
raise Error("There was an error projecting a node "
"at (%f, %f, %f) with normal (%f, %f, %f)."% (
X[i, 0], X[i, 1], X[i, 2],
chordDir[0], chordDir[1], chordDir[2]))
lePts[i] = up
# Check that points can form radius
d = numpy.linalg.norm(coords[:,0,:] - coords[:,1,:], axis=1)
r = numpy.linalg.norm(midPts - lePts, axis=1)
for i in range(nSpan):
if d[i] < 2*r[i]:
raise Error("Leading edge radius points are too far from the "
"leading edge point to form a circle between the "
"three points.")
# Add leading edge points and stack points into shape accepted by DVGeo
coords[:,2,:] = lePts
coords = numpy.vstack((coords[:,0,:], coords[:,1,:], coords[:,2,:]))
# Create the thickness constraint object
typeName = 'radiusCon'
if not typeName in self.constraints:
self.constraints[typeName] = OrderedDict()
if name is None:
conName = '%s_leradius_constraints_%d'%(self.name,len(self.constraints[typeName]))
else:
conName = name
self.constraints[typeName][conName] = RadiusConstraint(
conName, coords, lower, upper, scaled, scale, self.DVGeo,
addToPyOpt)
def addLocationConstraints1D(self, ptList, nCon,lower=None, upper=None,
scaled=False, scale=1.0, name=None,
addToPyOpt=True):
"""
Add a polyline in space that cannot move.
Parameters
----------
ptList : list or array of size (N x 3) where N >=2
The list of points forming a poly-line along which the
points will be fixed.
nCon : int
The number of points constraints to add
lower : float or array of size nCon
The lower bound for the constraint. A single float will
apply the same bounds to all constraints, while the array
option will use different bounds for each constraint. If
no value is provided, the bounds will default to the points,
giving equality constraints. Using the default is recommended.
upper : float or array of size nCon
The upper bound for the constraint. A single float will
apply the same bounds to all constraints, while the array
option will use different bounds for each constraint. If
no value is provided, the bounds will default to the points,
giving equality constraints. Using the default is recommended.
scaled : bool
Flag specifying whether or not the constraint is to be
implemented in a scaled fashion or not.
* scaled=True: The initial location of each location
constraint is defined to be 1.0. In this case, the lower
and upper bounds are given in multiple of the initial
location. lower=0.85, upper=1.15, would allow for 15%
change in each direction from the original location. However,
for initial points close to zero this blows up, so this should
be used with caution, therefore unscaled is the default.
* scaled=False: No scaling is applied and the phyical locations
must be specified for the lower and upper bounds.
scale : float or array of size nCon
This is the optimization scaling of the
constraint. Typically this parameter will not need to be
changed. If the location constraints are scaled, this
already results in well-scaled constraint values, and
scale can be left at 1.0. If scaled=False, it may changed
to a more suitable value if the resulting physical
location have magnitudes vastly different than O(1).
name : str
Normally this does not need to be set. Only use this if
you have multiple DVCon objects and the constriant names
need to be distinguished **or** you are using this set of
location constraints for something other than a direct
constraint in pyOptSparse.
addToPyOpt : bool
Normally this should be left at the default of True. If
the values need to be processed (modified) BEFORE they are
given to the optimizer, set this flag to False.
"""
self._checkDVGeo()
# Create the points to constrain
constr_line = pySpline.Curve(X=ptList, k=2)
s = numpy.linspace(0, 1, nCon)
X = constr_line(s)
# X shouls now be in the shape we need
if lower is None:
lower = X.flatten()
if upper is None:
upper = X.flatten()
# Create the location constraint object
typeName = 'locCon'
if not typeName in self.constraints:
self.constraints[typeName] = OrderedDict()
if name is None:
conName = '%s_location_constraints_%d'%(self.name,len(self.constraints[typeName]))
else:
conName = name
self.constraints[typeName][conName] = LocationConstraint(
conName, X, lower, upper, scaled, scale, self.DVGeo,
addToPyOpt)
def addProjectedLocationConstraints1D(self, ptList, nCon, axis, bias=0.5,
lower=None, upper=None,
scaled=False, scale=1.0, name=None,
addToPyOpt=True):
"""This is similar to addLocationConstraints1D except that the actual
poly line is determined by first projecting points on to the
surface in a similar manner as addConstraints1D, and then
taking the mid-point (or user specificed fraction) blend of
the upper and lower surface locations.
Parameters
----------
ptList : list or array of size (N x 3) where N >=2
The list of points from which to perform the projection
nCon : int
The number of points constraints to add
axis : list or array of length 3
The direction along which the projections will occur.
Typically this will be y or z axis ([0,1,0] or [0,0,1])
bias : float
The blending of the upper/lower surface points to use. Default
is 0.5 which is the average. 0.0 cooresponds to taking the
lower point, 1.0 the upper point.