-
Notifications
You must be signed in to change notification settings - Fork 2
/
TODO.rst
1326 lines (961 loc) · 58 KB
/
TODO.rst
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
* Rename `AirfoilCoefficients.Cl` -> `CL`? Airfoils are infinitely long
**symmetric** wings, and their coefficients are always produced using `β = 0`
so the roll and yaw coefficients are zero, meaning in the context of airfoils
you don't need `Cl` for the roll coefficient.
I think I went with `Cl` because Phillips uses `Cl` and `Cla` for the lift
coefficient and lift coefficient slope.
* Convert `extras.sample_paraglider_positions` to resample points; it's current
design is confusing. Just build a linear interpolator over the states and
resample.
Development
===========
Documentation
-------------
* Review the README template from "write the docs":
https://www.writethedocs.org/guide/writing/beginners-guide-to-docs/
* Highlight that all models can be copied out of the tree to serve as
a baseline to new models? For example, you can just copy and modify
`extras.wings.niviuk_hook3` since it don't use relative imports.
* Make sure to point out how I'm handling section dihedral angles. I made the
conscious decision to allow step changes, even though it produces overlap at
panel boundaries (as in my version of Belloc's reference wing). My assumption
is that the small overlap is less important that getting the panel
quarter-chord lines correct. You could try to account for airfoil thickness
and round the dihedral angles at the panel boundaries, but if you're allowing
continuously curving reference curves you'll have this issue anyway.
* Document why I decomposed the `foil` like I did. For example, you can't have
a pure `foil_geometry` because it needs the sections, but the sections are
most easily described by combining the section profiles with their
coefficients (so the geometry would "own" the section coefficient data).
Why did I make `Foil` own `b_flat`? I remember that making the design curves
in `foil_layout` normalized by `b_flat/2` allowed for simpler parametric
forms, but why not put `b_flat` in `foil_layout`? It's weird.
Docstrings
^^^^^^^^^^
* Sphinx uses type hints to create cross-references in the source, but it
doesn't do that automatically for the docstrings. For docstrings it requires
explicit ReST markup like ``:py:class:`pfh.glidersim.module.class```.
Assuming most consumers will view docstrings in the repl or their IDE, is
this clutter worth it? In the HTML output they can still click the linked
type in the function signature.
* Review source for docstring references to vectors like `\omega` and wrap them
in `\vec{}`. Also check the paraglider system dynamics class docstrings for
references like `a_B2e` or `omega_b2e` instead of their :math: equivalents.
* PEP8 recommends a docstring max line-length of 72, but that's a pain. I like
what numpy does: keep docstring length at 75, which means if you wrap normal
docstrings of top-level functions/classes by four spaces, then 75 puts you at
the 79 total width.
* Review all (sub)package, module, and class docstrings. They should have
summaries, descriptions, parameters, attributes, etc.
* How should I document `simulator.StateDynamics.state_dtype`?
* Verify function docstrings match the signatures (`darglint` would be
helpful, if only it worked)
* Functions that accept array inputs should take `array_like` and return
`ndarray`? Assume it's clear that a scalar input produces a scalar output?
Sphinx
^^^^^^
* `intersphinx` doesn't support equations yet (see sphinx #9483); once they do,
add links to `eq:6dof_state_dynamics` in `StateDynamics6a`, etc
* In Sphinx there is already a "Module index" (via ":ref:`modindex`"). The
"Library reference" section kind of overlaps with that?
* Review sphinx domains and the roles they define (eg, `:py:attr:` and
`:py:class:`; think of `:class:` as being a role scoped inside the `:py`
domain.) Not sure if I like fully-specified sphinx markup; it makes the
docstrings a lot more messy (eg, instead of `LineGeometry` it's a concrete
class like `:py:class:`pfh.glidersim.paraglider.ParagliderSystemDynamics6a`)
* Consider https://github.com/tox-dev/sphinx-autodoc-typehints
It would be great to deduplicate type information in the signature and
docstring, but it seems like formal `ndarray` type descriptions will always
be a mess compared to English summaries.
* I'm using `sphinx.ext.autosummary`, which uses `autodoc` under the hood.
A set of Jinja2 templates from [0] control the `autosummary` output. I'd
kind of like it if each module would list its classes in the contents tree
(left hand side of the `readthedocs` theme). I tried to achieve that by
overriding the `module.rst` template to include the ``:toctree:`` directive
to the ``.. autosummary::`` that's building up the classes in the module,
but that makes sphinx angry since it generates duplicate stubs for those
class definitions.
[0] https://github.com/sphinx-doc/sphinx/tree/master/sphinx/ext/autosummary/templates/autosummary
General
-------
* Review for standardized parameter order: `delta_*`, wind velocity, air
density, `g`, etc
* Should I rename `mass_properties` to `inertial_properties`?
* Review default parameters; are they justified? And if so, are they
consistent? For example, why is `rho_air` defaulted in some places but not
others?
* Performance: why is importing `pfh.glidersim` so slow?
* Replace `print` with `logging` where suitable
* I refer to "airfoil coordinates" in quite a few places. I'm not sure I like
that term. It's more like the "parameter" of a parametric curve. When I read
"coordinates" I think `xyz`.
* Vectorize `util.crossmat`?
* The control points need a redesign. I don't like stacking in them arrays
since that requires "magic" indexing (remembering which rows belong to each
component). I considered putting each component in a dictionary, but that
starts to weigh on the users of the class to know what to do with each
component. The paraglider classes shouldn't care what components are present
in the paraglider wing (the foil and the lines). You could use an idiom like
`moments = {key: np.cross(cps[key], forces[key]) for key in cp.keys}`, but
sprinkling that all over seems kind of icky to me. I have a vague feeling
a `ControlPoints` class might actually be warranted once the number of
components gets higher, but for now I'll just make each class keep track of
its own "magic" indices.
* Review the API for consistency
* Do the wing+glider functions always parametrize like (<wing stuff>,
<environment stuff>)? Can they?
* According to PEP8, module-level dunders should come after `from __future__`
imports but **before** normal imports. My `__all__` are technically wrong.
* I've been worried about how to let users write functions that support numpy
broadcasting. You can do it manually, with array shape manipulations, or you
can use `np.frompyfunc` and `np.vectorize`.
* Use a `.dev0` for in-development branches? See PEP440. Remove the `.dev0`
when releasing. This helps avoid situations where you look at a commit and
see a proper version number even though it's actually a development branch.
* I don't like that names like `v_W2h` and `v_W2b` (in the `resultant_force` of
`ParagliderHarness` and `ParagliderinWing`) don't clearly communicate that
they are ndarrays of vectors for each control point. Should I rename them
`v_W2CP`?
* Do I ever use `ValueError` when `TypeError` would be more appropriate?
Refactor
^^^^^^^^
* Rename `LineGeometry` -> `SuspensionLines`. They're more than just the
geometry; they provide mass properties and dynamics.
Split `SuspensionLines` into `suspension_lines.py` (for consistency)
* Rename `SimpleFoil` -> `ParagliderFoil`?
Reason being that it uses `FoilSections` which takes a `SimpleIntakes`.
Should I make a `class ParafoilSections(FoilSections)`, leaving the
`FoilSections` as a `Protocol`? It'd also let me isolate some of the stranger
stuff like the coefficient offsets (like `Cd_intakes`).
**More support for putting the `paraglider_*` modules into a package**
If I'm refactoring stuff, what happens to `SimpleFoil`? And does it make
sense to keep it scaled by `b_flat / 2`? Heck, does it make sense for `Foil`
to be opinionated about the choice of section index? **Shouldn't that belong
to the specific aircraft model choices?** Like, defining `s` using the length
of the `yz`-curve makes sense of paragliders, but maybe not for other wings.
It'd be nice if the top level `foil_` modules were unopinionated.
What about `SimpleFoil.mass_properties`? It knows parafoil-specific details
like upper vs lower surfaces. Should `mass_properties` be part of the
`Foil(Protocol)`? Who uses it outside of the immediate consumer
(`ParagliderWing` and `Paraglider`)? Same thing for the mesh generating
functions; they return separate upper/lower meshes. I guess the general
rule should be "don't try to generalize prematurely"; until somebody
outside the model-specific code needs it, don't add it to the Protocol.
* Reorganize all the paraglider-specific bits into a subpackage? So I'd have
`pfh.glidersim.paraglider` with `.wing`, `.lines`, `.harness`, etc? I'd
like to move the paraglider-specific state dynamics out of the `simulator`
module. Also, `paraglider.paraglider` is a bit weird; maybe
`.system_dynamics` and `.state_dynamics`? Also,
`ParagliderSystemDynamics6a` is kinda ridiculous.
Might be helpful to consider what it'd look like if I added other aircraft
like hang gliders, kites, or sailplanes.
* `extras/compute_polars.py`
Rename the module? It computes polar curves and wing coefficients.
Rename `plot_polar_curve` -> `polar_curve`; it doesn't plot anything.
Refactor the plotting in `plot_wing_coefficients` into `plots.py`
I should generalize the coefficients estimation in `belloc.py` and move it
into `glidersim`; should take reference lengths/areas and return
`{CX,CY,CZ,Cl,Cm,Cn,CXa,CYa,CZa,Cla,Cma,Cna}`. (Should it understand to start
from `alpha = 0` and work outwards to improve convergence? Same for `beta`.)
Would need to replace the `dict` with a numpy array with a structured dtype;
instead of direct indexing by value, you'd need something like
``coeffs[:,betas==specific_beta]['CZa']``. Also, some of the inputs might fail
to converge; I guess set their components to `nan`? (So instead of empty
arrays, you'd have `CXa = np.nan` for those entries.
.. code-block:: python
def compute_coefficients(alphas, betas, r_CP2LE, c_ref, S_ref):
# c_ref and S_ref are the reference length and area
alphas = np.asarray(alpha)
betas = np.asarray(betas)
dtype = ("CX", "CXa", ...)
coeffs = np.array(
shape=np.broadcast_shapes(alpha, beta),
np.nan,
dtype=dtype,
)
for ka, alpha in enumerate(alphas):
for kb, beta in enumerate(betas):
dF, dM = wing.aerodynamics(...)
F = dF.sum(...)
M = dM.sum(...) + np.cross(r_CP2RM, ...)
CX, CY, CZ = ...
CXa, CYa, CZa = ...
coefs[ka, kb] = (CX, CY, CZ, CXa, CYa, CZa)
What about control inputs? For polar curves you accelerator and brakes. Use
`kwargs`? Oh, and computing polar curves is NOT the same thing as
coefficient curves; don't confuse the two. Coefficient curves are usually
not associated with equilibrium states; polar curves are. Also, polar curves
depend on both aerodynamics **and gravity**; very different.
**Postpone**:: `belloc.py` uses `dict` indexed by `alpha` and `beta`
Static typing
-------------
* Remember to add a `py.typed` when ready. See PEP 561:
https://www.python.org/dev/peps/pep-0561/#packaging-type-information
* A variety of functions take a callable as an argument. Add the callable
parameters and return type, and document the signature in the docstring.
Gets messy when using numpy types though. For example, `FoilLayout` takes
a bunch of parameters that are `float | Callable`. You can type the callables
with `Callable[[npt.ArrayLike], npt.ArrayLike]` (using the new `numpy.typing`
module), but `mypy` still complains that I try to use `float(r_x)` even
though that call is guarded. Needs review.
* Use `typing.Literal` for parameters like `surface: typing.Literal["upper",
"lower", "camber", "airfoil"]`. Unlike the assertion-based checking, this
alerts the programmer when they're writing the code instead of crashing at
runtime (assuming they use `mypy`).
Numpy typing
^^^^^^^^^^^^
* Some functions have their return types marked `array_like`, but I think the
numpy convention is to return "scalar or ndarray".
* When numpy 1.21 is released, consider using `numpy.typing.NDArray` for
return values. It's a little ambiguous because if most functions return
a scalar given scalar inputs, but I think this is consistent with how most
numpy functions are typed. (related: numpy#19064)
* Type hint `array_like` inputs. Numpy 1.20 provides `npt.ArrayLike`, but it
allows all scalar types; I need hinting like `ArrayLike[Any, bool]`.
I think numpy v1.22 fixes this: "`ndarray`, `dtype`, and `number` are now
runtime-subscriptable", allowing `np.ndarray[Any, np.dtype[np.float64]]`.
Now I just need shape information; see numpy#16544
* Some parameters are `array_like of float`, but you can't set a type qualifier
with `npt.ArrayLike`. Is that expected to change?
* I don't like duplicating the type information in the docstrings, but at the
moment the formal types are much less friendly their informal docstring
counterparts.
Which types (formal signature or docstring) does Sphinx use?
Don't think I should hold my breath for `numpydoc` support:
https://github.com/numpy/numpydoc/issues/196
Looks like Napolean handles it though:
https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#type-annotations
Low priority
------------
* Review function parameters for compatibility with `array_like` arguments.
(Broadcasting is useful together with `np.meshgrid`, etc.)
Packaging
---------
* Merge `setup.cfg` into `pyproject.toml` once `setuptools` supports PEP 621
* Create `.git_archival.txt` once `setuptools_scm` supports the new
`git log --format=%(describe)` syntax in `git 2.35`
https://github.com/pypa/setuptools_scm/issues/578#issue-913435885
Adopt changes demonstrated in
https://github.com/pypa/setuptools_scm/pull/580/files (add
`.git_archival.txt`, update `.gitattributes` and `MANIFEST.in`, etc).
Probably need to bump `setuptools>=` in `pyproject.toml`.
* Publish to Zenodo, add *concept DOI* to README and `CITATION.cff`, and add
version DOI to thesis. After publishing the thesis, and its DOI to
`glidersim` and add equation references (eg, `Heatwole Eq:2.7`)
* Make `matplotlib` an optional dependency? Put it in a `plotting` extras.
Could lazy-load modules that import the library and present a user-friendly
error if a user tries to use them without having `matplotlib` installed.
Plots
-----
* Could `plot_3d_simulation_path` be refactored to plot labeled ndarray of
points that should be connected by lines? You could pass in `r_RM2O` as
a `(K,)`, or `(r_RM2O, r_LE2O)` as a `(2,K)` and plot lines pairwise. Once
you've got the general plotting function you could call it multiple times
with the same `ax` and create custom plotters for scenarios like it's
currently doing.
* In `plots.plot_foil` I have a `surface` parameter. Should I use `airfoil` or
`profile` for the profile surface? I'm using `airfoil` but in a way that
contradicts its use in `surface_xyz` (`plot_foil(surface='airfoil')`
actually plots the 'upper' and 'lower' surfaces).
* I'd sure like it if the 3D plots could use a `figsize` that wasn't square
(it wastes too much space). I think it's because `_set_axes_equal` uses
a radius, and all axes must contain that sphere. **Can you keep the equal
scaling property with different axes lengths?**
Testing
-------
* Testing component models using realistic designs is a real chore because true
values are difficult to calculate by hand. Instead, start with simplistic
models for fixtures, like an `AirfoilCoefficientsInterpolator` that just
returns `1`, a `FoilAerodynamics` that just returns `1`, a wing model that
weighs 1kg with the cg 1m below the wing, etc.
Foil aerodynamics
^^^^^^^^^^^^^^^^^
* Method that always produce a force of 1 in the direction of `v_W2b`?
* Method that always produces a yaw restoring force proportional to the
sideslip angle?
Foil
^^^^
* Test `mass_properties()` with simple shapes like spheres and cubes; should be
easy to verify surface areas, volumes, centers of mass, and moments of
inertia.
Paraglider wing
^^^^^^^^^^^^^^^
* `_compute_real_mass_properties`: difficult to test since it relies on
`canopy.mass_properties`.
Orientation
^^^^^^^^^^^
* Move the tests embedded in `orientation.py` into `test_orientation.py`
Tooling
-------
* Update `MANIFEST.in` to prune `.flake8`, `.gitignore`,
`.pre-commit-config.yaml`, `TODO.rst`, `requirements*.txt`, etc? Guess it
doesn't REALLY matter, they're small. What about `docs`? Is the `sdist`
strictly the content used to build the wheel? If so, it'd only need `src` and
a few config files.
* Try using `darglint` as a `flake8` plugin. As of 2021-01-01 this wasn't
working well, needs review.
Numba
^^^^^
* How much do 'C' vs 'F' arrays affect dot product performance? Enough for
Numba to warn me about it, at least. (see the error when defining
`orientation.quaternion_rotate`)
* Verify that setting `ai.flags.writeable = False` to silence Numpy warnings
about `broadcast_arrays` is okay. I'm not sure who triggers the warning, but
Numba doesn't seem to respect the `writeable` flag, so I need to verify that
`interp3d` doesn't modify its arguments.
* Benchmark `cross3` versus `np.cross` in Numba `v0.46`. As of 2019-12-16, that
function is roughly 60% slower on small arrays, and nearly 8x slower on
`10000x1000x3` arrays.
* Make `numba` a dev-only dependency by compiling the modules ahead of time?
See https://numba.readthedocs.io/en/stable/user/pycc.html Unfortunately, as
of Numba 0.55 "ahead-of-time" compilation doesn't support numpy ufuncs.
Airfoil
=======
* In `lingard1995RamairParachuteDesign` they suggest a NASA (NACA) LS(1)-0417
airfoil. Good idea to compare it's basic performance to the NACA 23015. If
I could create the airfoil data and use it for my Hook 3, even better. (At
least review its performance characteristics: great L/D at low alpha, and
dramatically smaller pitching moment across the range of alpha; interesting
to consider how that'd change equilibrium conditions, etc.)
* What are "low-speed airfoils"? The `NACA LS(1)-0417` (aka the `GA(W)-1`) is
considered low-speed, and is suggested in Lingard 1995 for ram-air
parachutes. The UIUC low-speed airfoil data catalogs cover such airfoils,
and they seem to use "low-speed" as synonymous with "low Reynolds number".
I'm seeing ranges from 60,000 to 500,000, depending on the document. In that
case, paragliders aren't particularly low-speed, but they're on the cusp,
and the tapered wing tips certainly delve into that range. But isn't the
"low Reynolds number" / "low-speed" assumption implying an assumption of
laminar flow? That is, they might **only** provide superior performance
**if** the flow is laminar? Seems like laminar flows are unlikely on
a paraglider.
* Different airfoils can have significantly different pitching coefficients
(eg, the NACA 24018 vs the LS(1)-0417), which should produce significantly
different equilibrium pitching angles. The arc of the wing will likely give
those different wings noticeably different dynamics in the presence of
a cross-wind, and **may have a significant impact on how the wing respond to
encountering a thermal during a turn**.
Geometry
--------
* Implement **accurate** `camber_curve` and `thickness` estimators.
This is mostly only an issue if I implement cell billowing (and thus ribs).
If I'm going to scale airfoils by changing their thickness, then I need the
correct camber and thickness functions. If I don't, then there will be weird
disjoint surfaces at small thickness changes (since you'll move from the
true surface to the version of that surface produced by estimates of its
thickness and camber). See branch `WIP_airfoil_curves`.
* Add some literature references. For NACA airfoils, there are:
* Abbott, "Theory of Wing Sections, Sec. 6
* https://www.hq.nasa.gov/office/aero/docs/rpt460/index.htm
* The XFOIL source code?
Coefficients
------------
* `GridCoefficients` and `GridCoefficients2` **require** the CSV to contain an
"airfoil index" column; you can't use them to interpolate coefficients for
a single airfoil geometry. They'll need special logic for calling the grid
interpolators.
* Implement clamping in *XFLR5Coefficients*, or at least warn that it doesn't
support clamping. The alternative is to add code to resample the coefficients
onto a regular regular grid and return a `GridCoefficients` (didn't I have
a script to do this already?), but that's a pain since the `GridCoefficients`
expects the coefficients set includes a range of `ai`; does it work if `ai=0`
always?
* Verify the polar curves, especially for flapped airfoils.
The airfoil data is still a bit of a mystery to me. I don't trust the XFOIL
output (at least not my use of it). It is extremely sensitive to tiny
changes in the number of points, the point distribution, and especially the
trailing edge gaps (which look like they should produce negligible
changes?). Just creating a nominal 23015 with the builtin generator then
removing the tiny TE gap causes the pitching moment in particular to change
dramatically.
* In `XFLR5Coefficients`, I could support XFOIL polars as well, but I'd need to
read the columns differently. Easy way to read the headers is with `names
= np.loadtxt(<filename>, skiprows=10, max_rows=1, dtype=str)`. I haven't
tested it with XFOIL polars though, might be missing some nuance.
Low priority
------------
* Let `NACA` use its explicit curve definitions. I'll have to compute `x` as
a function of arc-lengths, but beyond that use the actual functions instead
of relying on interpolated estimates. The annoying part will be calculating
the `profile_curve_normal` and `profile_curve_tangent` functions.
* Consider Gaussian quadratures or other more efficient arc-length methods?
* Why does `r` go clockwise? Why not just keep the counter-clockwise
convention? I do like that there is a sort of right-hand rule that points in
the +y direction though.
* Should I provide `r2d` and `d2r` functions? (Recall, `d` is the linear
distance along the entire surface, `r` is the linear distance along each
upper or lower surface) Suppose a user wanted to step along the curve in
equal steps; they'd need to convert those equally spaced `d` into `r`, which
is weird since the upper and lower surfaces use different spacings for `r`.
* Add Joukowski airfoil builders? Those are typically defined in terms of
their surface coordinates, not mean camber and thickness curves. Neat
airfoils though, conceptually. Very elegant.
FoilLayout
==========
* Define a `Protocol` for the `yz` parameter; it's VERY unclear that you need
an object that defines `__call__` and `derivative`; very weird API.
* Review the calculation of the projected span `b` in `FoilLayout.__init__`.
Should I use the furthest extent of the wing tips (typically happens at the
leading edge if the wing has positive torsion and arc anhedral), or should
I use `FoilLayout.b = xyz(1, r_yz(1))[1] - xyz(-1, r_yz(-1))[1]`?
* Should `FoilLayout` use the general form of the chord surface equation?
Maybe have another class that presents the simplified parametrization I'm
using for parafoil chord surfaces?
* Should I make the reference curves parametric functions? From a modelling
perspective, it would be convenient if the reference curves were "owned" by
the `LineGeometry`; it would allow things like making `yz` a function of
`delta_a` (ie, let the `LineGeometry` own `yz`), approximate "piloting with
the C's" control, etc. See branch `WIP_parametric_chords` for a mockup (and
a discussion of the limitations).
* `FoilLayout` requires the values to be proportional to `b_flat == 2`? **What
if you don't know `b_flat`? Do you need to compute the total length of `yz`
and re-normalize to that?** (I think I'm missing something here... As long as
everything is proportional, who cares? I'll need to look for anywhere that
uses `s` to stand in for `y`, but other than that, who cares? May want to
introduce an scaling value as a convenience for the user though.)
Parametric functions
--------------------
* Add `taper` as an alternative parameter in `EllipticalChord`
* Should `EllipticalArc`: accept the alternative pair `{b/b_flat,
max_anhedral}`? You often know b/b_flat from specs, and `max_anhedral` is
relatively easy to approximate from pictures.
* I don't like requiring `yz(s)` to be a functor that provides a `derivative`
method. I originally did it to match the `scipy` interpolator API
(`PchipInterpolator` in particular), but it's just awkward.
* Redefine the parameters in `EllipticalArc`? I've moved the paper away from
"dihedral/anhedral" angles since they're ambiguous. Euler angles are more
explicit, but it's not clear how to translate those into this usage.
FoilSections
============
* Rename `FoilSections` to `ParafoilSections`? They have intakes.
* The `FoilSections` is really a section interpolator. It's naming should make
that clear, ala `AirfoilGeometryInterpolator`. It's the same concept, except
it also adds `s` to beginning of the function signatures. While I'm at it,
it should take a dictionary `{s: {"geometry": g, "coefficients": c}}`, and
probably have a `symmetric : bool` property.
* Document `FoilSections`; focus on how it uses section indices with no
knowledge of spanwise coordinates (y-coordinates), it's xz coordinates have
not been scaled by the chord length, etc.
Heck, I need to document the entire stack: "a Foil is a combination of
`FoilLayout` and `FoilSections`, both of which define units that are
scaled by the span of the foil"
Profiles
--------
* I need to review everywhere I talk about airfoil "thickness" and ensure I'm
referring to "chordwise" or "camberwise" stations correctly. Some places
I mention "chordwise" stations, but glancing at the code it actually looks
like I'm computing `pc` as stations along the mean **camber** line.
* Who should be responsible for sanity checking the parameters for foil
surface coordinates? For example, `FoilSections.surface_xz` could do it, or
it could punt it downstream to the air intake functions (meaning each intake
implementation should duplicate the sanity checking code).
* Reconsider the design/purpose of `surface_xz`. The name implies that the
points are in foil frd (thus xyz, not just xy), but they're actually just
normal airfoil xy-coordinates. I could make it transform to frd, but there's
only one user of that: `SimpleFoil.surface_xyz`, which can do it itself
easily enough.
I was probably trying to maintain interface compatibility with
`AirfoilGeometry`, but all the `FoilSections` functions require a section
index anyway, so I'm not sure what I was going for.
Intakes
^^^^^^^
* Design review the air `intakes`. Possibly reconsider the name "intakes": this
concept doesn't require that `s_upper != s_lower`; it simply means the
upper/lower surface boundaries are different from the airfoil leading edge.
Might even be useful for *single surface* designs, which discard the lower
portion of the majority of the section profiles.
* Document the air intake functions (eg, `SimpleIntakes` and `_no_intakes`)
Coefficients
------------
* Performance: precompute the "size" of the air intakes for `FoilSections.Cd`.
It's surprisingly slow to recompute every call. Should probably be done as
part of the larger work to factor out coefficients modifiers. At the same
time I might want to consider the more general design issue of spanwise
variation of coefficient adjustments.
* Review `kulhanek2019IdentificationDegradationAerodynamic` and compare his
`C_d,f` to my "air intakes and skin friction drag" adjustments in
`FoilSections.Cd`
FoilAerodynamics
================
* Rename `reference_solution`. It should be a generic `dict` that the
aerodynamics method can stuff with whatever they need (like `solution`).
* Design review how the coefficient estimator signals non-convergence. (All
users that call `Phillips.__call__` should be exception-aware.)
* Building a linear model for the paraglider dynamics requires the *stability
derivatives* (derivatives of the coefficients with respect to `alpha` and
`beta`). The direct approach is finite differencing, but for a "more
economical approach", see "Flight Vehicle Aerodynamics" (Drela; 2014),
Sec:6.5.7, "Stability and control derivative calculation". For an example of
the defining equations for computing the linearized coefficients, check
"Appendix A" of :cite:`slegers2017ComparisonParafoilDynamic`. For a paper
with a set of numerical values, :cite:`toglia2010ModelingMotionAnalysis`.
* Aerodynamic centers exist for lifting bodies with linear lift coefficient
and constant pitching moment? How useful is this concept for paragliders?
(ie, over what range can you treat it as having an aerodynamic center, and
what value would there be?)
Phillips
--------
* Verify `Phillips._J`. It doesn't match the finite-difference approximation in
`Phillips._J_finite`. Should review `_J_finite` by comparing it to
`scipy.optimize.approx_fprime` (which, sadly, is univariate only).
* How should I handle a turning wing? (Non-uniform `u_inf`) Right now I just
use the central `V_rel` for `u_inf` and assume it's the same everywhere.
This is a general issue with aerodynamic methods that rely on the
*straight-wake assumption*. In general, vortex filaments do no have to be
straight lines, but the math is much simpler if they do (for example, with
Phillips they get to let two segments that share a node also share the
trailing vortex filament from that node, and because they're in opposite
directions the net shed vorticity is simply their sum). **The straight-wake
assumption is invalid for a rotating wing.** Faster turn rates will produce
larger error (but then most of these methods assume minimal spanwise flow, so
it's already a crapshoot).
Related: anytime you change speed and/or direction, you should shed an
additional *starting vortex*, which (I think?) should require additional
energy beyond what you'd expect from the steady-state aerodynamics.
For insight into the magnitude of the error, consult the manual for AVL
(`avl_dot.txt`). In the section "Unsteady Flow":
AVL assumes quasi-steady flow, meaning that unsteady vorticity shedding is
neglected. More precisely, it assumes the limit of small reduced
frequency, which means that any oscillatory motion (e.g. in pitch) must be
slow enough so that the period of oscillation is much longer than the time
it takes the flow to traverse an airfoil chord. This is true for
virtually any expected flight maneuver. Also, the roll, pitch, and yaw
rates used in the computations must be slow enough so that the resulting
relative flow angles are small. This can be judged by the dimensionless
rotation rate parameters, which should fall within the following practical
limits.
-0.10 < pb/2V < 0.10
-0.03 < qc/2V < 0.03
-0.25 < rb/2V < 0.25
These limits represent extremely violent aircraft motion, and are unlikely
to exceeded in any typical flight situation, except possibly during
low-airspeed aerobatic maneuvers. In any case, if any of these parameters
falls outside of these limits, the results should be interpreted with
caution.
* Review what happens if `v_W2f` is all zeros
* Add a `control_point_section_indices()` or somesuch to `Phillips`. Should
return a view of `s_cps` so `ParagliderWing` can stop grabbing it directly.
* Review Phillips paper: he says not to use the spatial midpoints of the
segments for the control points, and that "a significant improvement in
accuracy for a given number of elements can be achieved", especially near
the tips by placing the control points at the midpoints of the cosine
distribution angle instead of the midpoints of the segments. Look into that?
(Then again, I've been using a linear distribution in `s`, so I'm already
deviating quite a lot from his recommendation anyway.)
* Review `github/usaero/MachUpX`, commit `93ae2a7`: "Overcame singularity in
induced velocities by averaging the effective joint locations, thus forcing
continuity in the vortex sheet." Useful? He may just be talking about
discontinuities in the geometry, not the discontinuity at the wingtip.
* The `_hybrj` solver retries a bazillion times when it encounters a `nan`.
Can I use exceptions to abort early so I can use relaxation iterations
instead of letting `hybrj` try to brute force bad solutions? What if `_f`
threw an exception when it produces a `nan`, which is caught by Phillips to
initiate a relaxation solution? (This probably depends on how scipy calls
the Fortran code; not sure what happens to the Python exceptions.)
* If the target and reference are effectively the same, iteration will just
waste time (since you'll keep pushing the same target onto the stack). There
should be some kind of metric for deciding "the reference is too close to
the target to be of much use, just abort"
* Review the conditions for non-convergence. What are the primary causes, and
can they be mitigated? What are the average number of iterations for
convergence? Right now, convergence via iteration is uncommon: cases either
succeed, or they don't. It'd be nice to detect "non-convergence" ASAP.
* **Review the iteration design**: should I be interpolating `Gamma`?
* Verify the analytical Jacobian; right now the finite-difference
approximation disagrees with the analytical version (which isn't unexpected,
actually: it's computing `Cl_alpha` using finite differences of linearly
interpolated values of `Cl`).
* Using straight segments to approximate an curved wing will underestimate the
upper surface and overestimate the lower surface. It'd be interesting to
compute surface meshes for a range of `K` and (1) see how the error
accumulates for both surfaces, and (2) consider how the upper and lower
surfaces contribute to the airfoil coefficients. For example, if the
dominant contributor to the section lift coefficient is the pressure over
the upper surface of the airfoil, you'd expect an underestimate of the
segment upper surface area to underestimate the segment lift coefficient,
but I'm not sure what conclusions you could reliably produce from such
a crude measure.
* Profile and optimize
* For example, ``python -m cProfile -o belloc.prof belloc.py``, then ``>>>
p = pstats.Stats('belloc.prof'); p.sort_stats('cumtime').print_stats(50)``
* Do the matrices used in the `einsum` calls have the optimal in-memory
layout? Consider the access patterns and verify they are contiguous in the
correct dimensions (ie, `C` vs `F` contiguous; see ``ndarray.flags``)
* Phillips' could always use more testing against XFLR5 or similar. I don't
have geometry export yet, but simple flat wings should be good for comparing
my Phillips implementation against the VLM methods in XFLR5.
Foil
====
* HIGH: should there be a `FoilGeometry` class? Right now `SimpleFoil` combines
the layout, sections, and aerodynamics, and you set aerodynamics to `None` if
you don't care. A bit weird. I think I need a class `Foil(FoilGeometry,
FoilAerodynamics)` or similar. I've NEVER liked this design where
`SimpleFoil` passes `self` to initialize the `FoilAerodynamics`.
* HIGH: I refer to `FoilGeometry` in several places, but that class doesn't
exist. There is only `SimpleFoil` in `foil.py`. Define a `Protocol`. What are
the essential needs? `section_orientation, chord_length, surface_xyz`. More?
I think the least constraining view is "profiles as a function of section
index positioned along some line".
* HIGH: the name `SimpleFoil` is peculiar. Simple compared to what? (I think
I was originally planning to create a `Parafoil` class which includes the
cells and accounts for cell billowing).
* HIGH: In `Foil.surface_xyz`, I use `airfoil` for the profile surfaces, but in
my paper I'm referring to the airfoil as the unit-chord shape and "section
profile" for the scaled shape. Should I rename `airfoil` -> `profile`?
* Refactor `mesh_vertex_lists` to work on any of the surfaces (`{upper, lower,
airfoil, chord, camber}`)? Right now it just assumes you want both `upper`
and `lower`.
* The mesh functions don't support airfoil indices (they fix `ai = 0`)
* Should `S_flat`, `b`, etc really be class properties? Class properties don't
support parameters, which means these break for parametric reference curves
(eg, if arc anhedral is a function of `delta_a`). You could require users to
specify "default parameters" for any extra parameters in the reference
curves, but somehow that feels wrong.
Inertia
^^^^^^^
* The new mesh-based `SimpleFoil.mass_properties` uses triangles which are not
symmetric outwards from the central section, so small numerical differences
produce significantly non-zero Ixy/Iyz terms in the inertia tensors. Once
I fix this I should also remove the manual symmetry corrections in
`ParagliderWing.__init__`.
* Why doesn't the old `mass_properties` agree with the mesh-based method? See
`scripts/validate_inertia.properties.py`
* Refactor the mesh sampling so I don't have to duplicate it in both
`mass_properties` and `_mesh_vertex_lists`. Probably best to generalize
`mesh_vertex_lists` to work on {"upper", "lower", "airfoil"} and add
a different function that outputs the wing mesh to a file.
Cells
^^^^^
This is a catch-all group. Right now I'm using the idealized `FoilLayout`
directly, but real parafoils are comprised of cells, where the ribs provide
internal structure and attempt to produce the desired airfoil cross-sections,
but deformations (billowing, etc) cause deviations from that ideal shape.
Long term, I'd like to combine the idealized chord surface with a set of ribs
and produce the set of (approximately) deformed cells. There are many tasks
here:
* Replace explicit `AirfoilGeometry` references (eg,
`canopy.airfoil.geometry`) with a function that returns the profile as
a function of section index.
* Define a set of rib types (vertical ribs, v-ribs, lateral bands, etc)
* Define a set of heuristics that approximate the inflated profiles for each
cell (ie, profiles between the vertical ribs)
* Write functions that compute points on the chords and surfaces of sections
from inflated or deflated cells. **There is a lot of sublety here.** There
needs to be a mapping between the inflated and deflated section indices, so
you can't just use the "flattened" values; the cell widths themselves
change.
Some considerations:
* I'd like to at least try to maintain the surface areas during billowing; you
can explicitly ignore the creases that will develop, but the total surface
area shouldn't change THAT much. (Perhaps use the "mesh to cell surface
area" function to compute the `thickness_ratio` that would maintain
a constant surface area for the inflated and deflated cell surfaces?)
Related thought: if the upper surfaces maintain the same area, do the lower
surfaces also have the same area? Multiplying the thickness by a constant
seems like it should be a linear function, so I *think* the lower and upper
surfaces should both be correct, but it's worth checking.
* Try to anticipate some of the effects of billowing. For example, compare the
performance of a normal `24018` to a 15% increased thickness `24018` using
XFLR5 (which simply scales the airfoil by a constant factor). Make a list of
anticipated deviations compared to the idealized `FoilLayout`. (decreased
lift/drag ratio, etc)
* How a cell compresses during inflation depends on the shape of the parafoil
(line loadings, etc). (ref: `altmann2019FluidStructureInteractionAnalysis`)
Deformations
^^^^^^^^^^^^
* To warp the trailing edge, could you warp the mean camber line instead of
the surfaces themselves, then constrain to maintain constant curve length?
* Starting with the `FoilLayout`, how hard would it be to warp the central
sections to produce a "weight shift" effect?
* Is it a fools errand to support lifting-line methods in the presence of
deformations? Cell billowing, weight shift, trailing edge braking: they all
produce deformed profiles, adding many dimensions to the coefficients table.
Meshes
^^^^^^
* I think my mesh functions are broken? The lower surface gave a bunch of "Bad
face in mesh" errors that crashed Blender 2.82. See `notes-2020w19` for more
details.
* Other issues:
* The normals of my upper faces are backwards? (They point in, not out.)
* When do you want triangles versus quadrilaterals? You can cut the number
of edges and faces in half with "Edit -> Face -> Tris to Quads"
* Refactor the "mesh" functions to take the vertices as inputs.
This would allow the user to generate a mesh over a subset of the foil, and
(more importantly) allow me to generate a mesh over a single cell (which you
can then use to compute the surface area.
* Rewrite the vertex generator functions to take `s` and `sa` as parameters.
This would enable generating a mesh over the surfaces of individual cells
(should work with inflated or deflated cells) and compute their surface area.
(The surface area of a cell could be useful for estimating the inflated cell
surfaces.)
* Write a function to compute the surface area of a mesh
Not hard: `.5 * cross(AB, AC)` or some such, right?
Would allow me to compute the `thickness_ratio` distribution (for the
inflated cells) that would maintain a constant surface area.
Lower priority
^^^^^^^^^^^^^^
* I claim that `FoilGeometry` is defined as having the central chord leading
edge at `x = 0` and that the central chord lies in the xy-plane, **by
definition**, but I never enforce that. I do shift the leading edge to the
origin, but I don't derotate the global wing.
I guess it'd be good enough to just require that `torsion(s=0) = 0`, but
I guess I could also just compute `torsion(s=0)` and subtract that from all
torsions, thus "centering" the twist in the same manner as the origin.
* Move `InterpolatedArc` from `belloc.py` into `foil.py` and modify it to use
intelligent resampling (near the given points, not just a blind resample).
* Review the API: accept any of `{b, b_flat, S, S_flat}` as scaling factors
Low Priority
^^^^^^^^^^^^
* Use a library like `https://github.com/orbingol/NURBS-Python` to export STL,
NURBS, etc?
* Add an example for exporting the triangle mesh to `vtkPolyData` (or whatever
the correct data structure would be). Would make it easier to interface with
OpenFOAM (you can import the mesh into Blender and export an STL, but I'm
sure there are easier ways to go about it, like `NURBS-Python`).
* Is the "mean aerodynamic chord" a useful concept for arched wings?
* Should the "projected surface area" methods take pitch angle as a parameter?
I'm not sure what most paraglider wing manufacturers use for the projected
area. My definitions requires that the central chord is parallel to the
xy-plane, but I imagine some manufacturers would use the equilibrium angle
of the wing. It's more in-line with what you'd use for classical aerodynamic
analysis, and it's essential constant regardless of load.