flightanalysis 0.3.16__tar.gz → 0.3.18__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/PKG-INFO +3 -3
  2. flightanalysis-0.3.18/examples/tailslide.py +25 -0
  3. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/pyproject.toml +3 -3
  4. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/analysis/el_analysis.py +15 -0
  5. flightanalysis-0.3.18/src/flightanalysis/analysis/manoeuvre_analysis/alignment.py +126 -0
  6. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/analysis/manoeuvre_analysis/basic.py +74 -26
  7. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/analysis/manoeuvre_analysis/complete.py +85 -45
  8. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/analysis/manoeuvre_analysis/scored.py +13 -5
  9. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/analysis/sch_analysis.py +41 -16
  10. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/builders/elbuilders.py +11 -1
  11. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/builders/manbuilder.py +17 -12
  12. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/eldef.py +12 -0
  13. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/mandef.py +10 -11
  14. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/manparm.py +3 -3
  15. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/scheddef.py +2 -2
  16. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/elements/__init__.py +2 -1
  17. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/elements/element.py +2 -8
  18. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/elements/line.py +8 -11
  19. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/elements/loop.py +9 -5
  20. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/elements/snap.py +1 -4
  21. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/elements/stall_turn.py +10 -18
  22. flightanalysis-0.3.18/src/flightanalysis/elements/tail_slide.py +90 -0
  23. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/manoeuvre.py +14 -20
  24. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/box/box.py +8 -8
  25. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/downgrade.py +35 -29
  26. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/measurement.py +6 -10
  27. flightanalysis-0.3.18/src/flightanalysis/scoring/reffuncs.py +40 -0
  28. flightanalysis-0.3.18/src/flightanalysis/scoring/results/__init__.py +4 -0
  29. flightanalysis-0.3.18/src/flightanalysis/scoring/results/dgplot.py +40 -0
  30. flightanalysis-0.3.18/src/flightanalysis/scoring/results/elements_results.py +67 -0
  31. flightanalysis-0.3.18/src/flightanalysis/scoring/results/manoeuvre_results.py +84 -0
  32. flightanalysis-0.3.18/src/flightanalysis/scoring/results/result.py +259 -0
  33. flightanalysis-0.3.18/src/flightanalysis/scoring/results/results.py +108 -0
  34. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/results.py +96 -60
  35. flightanalysis-0.3.18/tests/data/00000100.ajson +249252 -0
  36. flightanalysis-0.3.18/tests/test_box.py +28 -0
  37. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_criteria.py +2 -0
  38. flightanalysis-0.3.18/tests/test_schedule/test_element/__init__.py +0 -0
  39. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/test_schedule_element_loop.py +16 -26
  40. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/test_schedule_element_spin.py +0 -11
  41. flightanalysis-0.3.18/tests/test_schedule/test_manoeuvre.py +41 -0
  42. flightanalysis-0.3.16/src/flightanalysis/analysis/manoeuvre_analysis/alignment.py +0 -125
  43. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/__init__.py +0 -13
  44. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/attitude.py +0 -108
  45. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/curvature.py +0 -47
  46. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/measurement.py +0 -160
  47. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/positioning.py +0 -40
  48. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/roll.py +0 -92
  49. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/speed.py +0 -25
  50. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/stall.py +0 -92
  51. flightanalysis-0.3.16/src/flightanalysis/scoring/measurements/track.py +0 -150
  52. flightanalysis-0.3.16/src/flightanalysis/scoring/reffuncs.py +0 -6
  53. flightanalysis-0.3.16/src/flightanalysis/scoring/selectors.py +0 -118
  54. flightanalysis-0.3.16/src/flightanalysis/scoring/smoothing.py +0 -102
  55. flightanalysis-0.3.16/tests/test_box.py +0 -49
  56. flightanalysis-0.3.16/tests/test_schedule/test_schedule_manoeuvre.py +0 -24
  57. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/.github/workflows/publish_pypi.yml +0 -0
  58. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/.gitignore +0 -0
  59. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/LICENSE +0 -0
  60. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/README.md +0 -0
  61. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/examples/__init__.py +0 -0
  62. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/examples/making_elements.py +0 -0
  63. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/__init__.py +0 -0
  64. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/analysis/__init__.py +0 -0
  65. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/analysis/manoeuvre_analysis/__init__.py +0 -0
  66. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/analysis/manoeuvre_analysis/analysis.py +0 -0
  67. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/base/ref_funcs.py +0 -0
  68. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/base/utils.py +0 -0
  69. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/builders/__init__.py +0 -0
  70. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/builders/example/__init__.py +0 -0
  71. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/builders/example/schedule_builder.py +0 -0
  72. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/__init__.py +0 -0
  73. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/collectors.py +0 -0
  74. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/dg_applicator.py +0 -0
  75. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/manoption.py +0 -0
  76. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/operations/__init__.py +0 -0
  77. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/operations/funopp.py +0 -0
  78. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/operations/itemopp.py +0 -0
  79. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/operations/mathopp.py +0 -0
  80. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/operations/operation.py +0 -0
  81. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/definition/operations/sumopp.py +0 -0
  82. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/elements/spin.py +0 -0
  83. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/py.typed +0 -0
  84. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/schedule.py +0 -0
  85. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/__init__.py +0 -0
  86. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/box/__init__.py +0 -0
  87. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/box/rectangular_box.py +0 -0
  88. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/box/triangular_box.py +0 -0
  89. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/__init__.py +0 -0
  90. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/criteria.py +0 -0
  91. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/exponential.py +0 -0
  92. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/inter/__init__.py +0 -0
  93. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/inter/combination.py +0 -0
  94. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/inter/comparison.py +0 -0
  95. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/intra/__init__.py +0 -0
  96. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/intra/bounded.py +0 -0
  97. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/intra/continuous.py +0 -0
  98. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/intra/peak.py +0 -0
  99. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/criteria/intra/single.py +0 -0
  100. /flightanalysis-0.3.16/tests/__init__.py → /flightanalysis-0.3.18/src/flightanalysis/scoring/results/summary.py +0 -0
  101. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/scoring/visibility.py +0 -0
  102. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/src/flightanalysis/version.py +0 -0
  103. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/EmailedBox.f3a +0 -0
  104. {flightanalysis-0.3.16/tests/test_schedule → flightanalysis-0.3.18/tests}/__init__.py +0 -0
  105. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/conftest.py +0 -0
  106. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/data/manual_F3A_P23.json +0 -0
  107. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/data/old_json.json +0 -0
  108. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/data/p23.BIN +0 -0
  109. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/data/p23_box.f3a +0 -0
  110. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/data/p23_fc.json +0 -0
  111. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/data/p23_flight.json +0 -0
  112. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/data/scored_fcj.json +0 -0
  113. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/data/unscored_fcj.json +0 -0
  114. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_operations.py +0 -0
  115. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_rfuncs.py +0 -0
  116. {flightanalysis-0.3.16/tests/test_schedule/test_element → flightanalysis-0.3.18/tests/test_schedule}/__init__.py +0 -0
  117. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/loop_analysis.json +0 -0
  118. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/p23_th_e0.csv +0 -0
  119. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/p23_th_e0.json +0 -0
  120. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/p23_th_e0_template.csv +0 -0
  121. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/test_schedule_element.py +0 -0
  122. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/test_schedule_element_line.py +0 -0
  123. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/test_schedule_element_snap.py +0 -0
  124. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/tests/test_schedule/test_element/test_schedule_element_stallturn.py +0 -0
  125. {flightanalysis-0.3.16 → flightanalysis-0.3.18}/uv.lock +0 -0
@@ -1,18 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flightanalysis
3
- Version: 0.3.16
3
+ Version: 0.3.18
4
4
  Summary: Python tools for Analysis of Flight Data
5
5
  Author-email: Thomas David <thomasdavid0@gmail.com>
6
6
  License-File: LICENSE
7
7
  Requires-Python: >=3.12
8
8
  Requires-Dist: fastdtw>=0.3.4
9
- Requires-Dist: flightdata>=0.2.25
9
+ Requires-Dist: flightdata>=0.3.0
10
10
  Requires-Dist: joblib>=1.4.2
11
11
  Requires-Dist: json-stream>=2.3.2
12
12
  Requires-Dist: loguru>=0.7.2
13
13
  Requires-Dist: numpy>=2.1.3
14
14
  Requires-Dist: pandas>=2.2.3
15
- Requires-Dist: pfcschemas>=0.1.7
15
+ Requires-Dist: pfcschemas>=0.1.10
16
16
  Requires-Dist: scipy>=1.14.1
17
17
  Requires-Dist: simplejson>=3.19.3
18
18
  Provides-Extra: dataflash
@@ -0,0 +1,25 @@
1
+ from flightanalysis import Loop, Line, Snap, Elements, Manoeuvre
2
+ import numpy as np
3
+ from flightdata import State
4
+ import geometry as g
5
+ from plotting import plotsec
6
+
7
+ from flightanalysis.elements.tail_slide import TailSlide
8
+
9
+
10
+ # man = Manoeuvre(Elements([
11
+ # Line('entry_line', 30, 60, 0),
12
+ # Loop('loop', 30, np.pi/2, 50, 0, 0),
13
+ # Line("upline", 30, 100, 0),
14
+ # TailSlide("tslide", -5, np.pi, np.radians(30), np.pi),
15
+ # Line("downline", 30, 100, 0),
16
+ # Loop("loop2", 30, np.pi/2, 50, 0,0 )
17
+ # ]), Line('exit_line', 30, 60, 0))
18
+ #tp = man.create_template(State.from_transform(g.Transformation()))
19
+
20
+
21
+ tp = TailSlide("tslide", -5, -np.pi, np.radians(30), np.pi).create_template(
22
+ State.from_transform(g.Transformation(g.Euler(0, -np.pi/2, 0)))
23
+ )
24
+
25
+ plotsec(tp, nmodels=10, scale=1).show()
@@ -1,19 +1,19 @@
1
1
  [project]
2
2
  name = "flightanalysis"
3
- version="0.3.16"
3
+ version="0.3.18"
4
4
  description = "Python tools for Analysis of Flight Data"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Thomas David", email = "thomasdavid0@gmail.com" }]
7
7
  requires-python = ">=3.12"
8
8
  dependencies = [
9
9
  "fastdtw>=0.3.4",
10
- "flightdata>=0.2.25",
10
+ "flightdata>=0.3.0",
11
11
  "joblib>=1.4.2",
12
12
  "json-stream>=2.3.2",
13
13
  "loguru>=0.7.2",
14
14
  "numpy>=2.1.3",
15
15
  "pandas>=2.2.3",
16
- "pfcschemas>=0.1.7",
16
+ "pfcschemas>=0.1.10",
17
17
  "scipy>=1.14.1",
18
18
  "simplejson>=3.19.3",
19
19
  ]
@@ -15,6 +15,18 @@ class ElementAnalysis:
15
15
  tp: State
16
16
  ref_frame: g.Transformation
17
17
 
18
+ def update(self, new_fl: State):
19
+ new_el = self.el.match_intention(self.ref_frame, new_fl)
20
+ new_tp = new_el.create_template(self.tp[0], new_fl.time)
21
+ return ElementAnalysis(
22
+ self.edef,
23
+ self.mps,
24
+ new_el,
25
+ new_fl,
26
+ new_tp,
27
+ self.ref_frame
28
+ )
29
+
18
30
  def plot_3d(self, **kwargs):
19
31
  from plotting import plotsec
20
32
  return plotsec(dict(fl=self.fl, tp=self.tp), **kwargs)
@@ -34,6 +46,9 @@ class ElementAnalysis:
34
46
  g.Transformation.from_dict(data['ref_frame'])
35
47
  )
36
48
 
49
+ def score_dg(self, dg: str):
50
+ return self.edef.dgs[dg](self.el, self.fl, self.tp)
51
+
37
52
  def intra_score(self):
38
53
  return self.edef.dgs.apply(self.el, self.fl, self.tp) #[dg.apply(self.el.uid + (f'_{k}' if len(k) > 0 else ''), self.fl, self.tp) for k, dg in self.edef.dgs.items()]
39
54
 
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from flightdata import State, align
6
+
7
+ from flightanalysis.definition import ManDef
8
+ from flightanalysis.elements import Element
9
+ from flightanalysis.manoeuvre import Manoeuvre
10
+
11
+ from ..el_analysis import ElementAnalysis
12
+ from .basic import Basic
13
+
14
+
15
+ @dataclass(repr=False)
16
+ class Alignment(Basic):
17
+ manoeuvre: Manoeuvre | None
18
+ templates: dict[str, State] | None
19
+
20
+ @property
21
+ def template(self):
22
+ return State.stack(self.templates, "element")
23
+
24
+ @property
25
+ def template_list(self):
26
+ return list(self.templates.values())
27
+
28
+ def get_ea(self, name_or_id: str | int) -> ElementAnalysis:
29
+ el: Element = self.manoeuvre.elements[name_or_id]
30
+
31
+ return ElementAnalysis(
32
+ self.mdef.eds[name_or_id],
33
+ self.mdef.mps,
34
+ el,
35
+ self.flown.element[el.uid],
36
+ self.templates[el.uid],
37
+ self.templates[el.uid][0].transform,
38
+ )
39
+
40
+ def __getattr__(self, name) -> ElementAnalysis:
41
+ return self.get_ea(name)
42
+
43
+ def __getitem__(self, name_or_id) -> ElementAnalysis:
44
+ return self.get_ea(name_or_id)
45
+
46
+ def run_all(
47
+ self, optimise_aligment=True, force=False
48
+ ) -> Alignment | Complete | Scored:
49
+ if self.__class__.__name__ == "Scored" and force:
50
+ self = self.downgrade()
51
+ while self.__class__.__name__ != "Scored":
52
+ self = (
53
+ self.run(optimise_aligment)
54
+ if isinstance(self, Complete)
55
+ else self.run()
56
+ )
57
+ return self
58
+
59
+ @staticmethod
60
+ def from_dict(ajman: dict) -> Alignment | Basic:
61
+ basic = Basic.from_dict(ajman)
62
+ if isinstance(basic, Basic) and "manoeuvre" in ajman and ajman["manoeuvre"]:
63
+ if "template" in ajman:
64
+ if set(ajman["template"].keys()) == set(
65
+ [el["uid"] for el in ajman["manoeuvre"]["elements"]] + ["exit_line"]
66
+ ):
67
+ return Alignment(
68
+ **basic.__dict__,
69
+ manoeuvre=Manoeuvre.from_dict(ajman["manoeuvre"]),
70
+ templates={
71
+ k: State.from_dict(v) for k, v in ajman["template"].items()
72
+ }
73
+ if "templates" in ajman
74
+ else None,
75
+ )
76
+ return basic
77
+
78
+ def to_dict(self, basic: bool = False) -> dict:
79
+ _basic = super().to_dict(basic)
80
+ if basic:
81
+ return _basic
82
+ return dict(
83
+ **_basic,
84
+ manoeuvre=self.manoeuvre.to_dict(),
85
+ template={k: tp.to_dict(True) for k, tp in self.templates.items()},
86
+ )
87
+
88
+ def run(self) -> Alignment | Complete:
89
+ if "element" not in self.flown.labels.lgs:
90
+ return self._run(True)[1]
91
+ return self._run(False)[1].proceed()
92
+
93
+ def _run(self, mirror=False, radius=10) -> Alignment:
94
+ res = align(self.flown, self.template, radius, mirror)
95
+ return res.dist, self.update(res.aligned)
96
+
97
+ def update(self, aligned: State) -> Alignment:
98
+ man, tps = self.manoeuvre.match_intention(self.template_list[0][0], aligned)
99
+ mdef = ManDef(
100
+ self.mdef.info,
101
+ self.mdef.mps.update_defaults(man),
102
+ self.mdef.eds,
103
+ self.mdef.box,
104
+ )
105
+ return Alignment(self.id, self.schedule_direction, aligned, mdef, man, tps)
106
+
107
+ def _proceed(self) -> Complete:
108
+ if "element" in self.flown.labels.keys():
109
+ correction = self.mdef.create()
110
+ return Complete(
111
+ self.id,
112
+ self.schedule_direction,
113
+ self.flown,
114
+ self.mdef,
115
+ self.manoeuvre,
116
+ self.template,
117
+ correction,
118
+ correction.create_template(self.template[0], self.flown),
119
+ )
120
+ else:
121
+ return self
122
+
123
+
124
+
125
+ from .complete import Complete # noqa: E402
126
+ from .scored import Scored # noqa: E402
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
+ import stat
4
5
  from typing import Annotated
6
+ from xmlrpc.client import boolean
5
7
 
6
8
  import numpy as np
7
9
 
@@ -9,8 +11,12 @@ import geometry as g
9
11
  from flightdata import State
10
12
  from schemas.positioning import Direction, Heading
11
13
  from flightanalysis.definition import ManDef, ManOption
14
+ from schemas.sinfo import ScheduleInfo
12
15
 
13
16
  from .analysis import Analysis
17
+ from schemas import MA, fcj
18
+ from importlib.metadata import version
19
+
14
20
 
15
21
 
16
22
  @dataclass
@@ -28,10 +34,13 @@ class Basic(Analysis):
28
34
 
29
35
  def __str__(self):
30
36
  res = f"{self.__class__.__name__}({self.id}, {self.mdef.info.short_name})"
31
- if hasattr(self, "scores"):
32
- res = res[:-1] + f", {', '.join([f'{k}={v:.2f}' for k, v in self.scores.score_summary(3, False).items()])})"
37
+ if isinstance(self, Scored):
38
+ res = (
39
+ res[:-1]
40
+ + f", {', '.join([f'{k}={v:.2f}' for k, v in self.scores.score_summary(3, False).items()])})"
41
+ )
33
42
  return res
34
-
43
+
35
44
  def __repr__(self):
36
45
  return str(self)
37
46
 
@@ -39,25 +48,20 @@ class Basic(Analysis):
39
48
  """Run the analysis to the final stage"""
40
49
  drs = [r._run(True) for r in self.run()]
41
50
 
42
- dr = drs[np.argmin([dr[0] for dr in drs])]
51
+ dr: Alignment = drs[np.argmin([dr[0] for dr in drs])][1]
43
52
 
44
- return dr[1].run_all(optimise_aligment, force)
53
+ return dr.run_all(optimise_aligment, force)
45
54
 
46
55
  def proceed(self) -> Complete:
47
56
  """Proceed the analysis to the final stage for the case where the elements have already been labelled"""
48
- if (
49
- "element" not in self.flown.data.columns
50
- or self.flown.data.element.isna().any()
51
- or not isinstance(self, Basic)
52
- ):
57
+ if "element" not in self.flown.labels.keys() or not isinstance(self, Basic):
53
58
  return self
54
59
 
55
60
  mopt = ManOption([self.mdef]) if isinstance(self.mdef, ManDef) else self.mdef
56
- elnames = self.flown.data.element.unique().astype(str)
61
+
62
+ elnames = list(self.flown.labels.element.keys())
57
63
  for md in mopt:
58
- if np.all(
59
- [np.any(np.char.startswith(elnames, k)) for k in md.eds.data.keys()]
60
- ):
64
+ if len(elnames) != len(md.eds) or np.all([elnames[i] == k for i, k in enumerate(md.eds.data.keys())]):
61
65
  mdef = md
62
66
  break
63
67
  else:
@@ -66,22 +70,19 @@ class Basic(Analysis):
66
70
  )
67
71
 
68
72
  itrans = self.create_itrans()
69
- man, tp = (
73
+ man, tps = (
70
74
  mdef.create()
71
75
  .add_lines()
72
76
  .match_intention(State.from_transform(itrans), self.flown)
73
77
  )
74
78
  mdef = ManDef(mdef.info, mdef.mps.update_defaults(man), mdef.eds, mdef.box)
75
- corr = mdef.create().add_lines()
76
79
  return Complete(
77
80
  self.id,
78
81
  self.schedule_direction,
79
82
  self.flown,
80
83
  mdef,
81
84
  man,
82
- tp,
83
- corr,
84
- corr.create_template(itrans, self.flown),
85
+ tps,
85
86
  )
86
87
 
87
88
  @staticmethod
@@ -89,25 +90,34 @@ class Basic(Analysis):
89
90
  return Basic(
90
91
  id=data["id"],
91
92
  schedule_direction=Heading[data["schedule_direction"]]
92
- if (data["schedule_direction"] and data['schedule_direction'] != "Infer")
93
+ if (data["schedule_direction"] and data["schedule_direction"] != "Infer")
93
94
  else None,
94
95
  flown=State.from_dict(data["flown"]),
95
96
  mdef=ManDef.from_dict(data["mdef"]),
96
97
  )
97
98
 
98
- def to_dict(self, basic:bool=False) -> dict:
99
+ def to_dict(self, basic: bool = False) -> dict:
99
100
  return dict(
100
101
  id=self.id,
101
- schedule_direction=self.schedule_direction.name if self.schedule_direction else None,
102
- flown=self.flown.to_dict(),
102
+ schedule_direction=self.schedule_direction.name
103
+ if self.schedule_direction
104
+ else None,
105
+ flown=self.flown.to_dict(True),
103
106
  **(dict(mdef=self.mdef.to_dict()) if not basic else {}),
104
107
  )
105
108
 
106
109
  def create_itrans(self) -> g.Transformation:
107
- if self.schedule_direction and self.mdef.info.start.direction is not Direction.CROSS:
108
- entry_direction = self.mdef.info.start.direction.wind_swap_heading(self.schedule_direction)
110
+ if (
111
+ self.schedule_direction
112
+ and self.mdef.info.start.direction is not Direction.CROSS
113
+ ):
114
+ entry_direction = self.mdef.info.start.direction.wind_swap_heading(
115
+ self.schedule_direction
116
+ )
109
117
  else:
110
- entry_direction = Heading.infer(self.flown[0].att.transform_point(g.PX()).bearing()[0])
118
+ entry_direction = Heading.infer(
119
+ self.flown[0].att.transform_point(g.PX()).bearing()[0]
120
+ )
111
121
 
112
122
  return g.Transformation(
113
123
  self.flown[0].pos,
@@ -133,6 +143,44 @@ class Basic(Analysis):
133
143
  )
134
144
  return als
135
145
 
146
+ def export_ma(self, schedule: ScheduleInfo, history: dict = None) -> MA:
147
+ return MA(
148
+ **self.to_dict(),
149
+ name=self.mdef.info.short_name,
150
+ schedule=schedule,
151
+ history={
152
+ **(history if history else {}),
153
+ **(
154
+ {
155
+ version("fatuning"): fcj.ManResult.model_validate(
156
+ self.fcj_results()
157
+ )
158
+ }
159
+ if self.__class__.__name__ == "Scored"
160
+ else {}
161
+ ),
162
+ },
163
+ )
164
+
165
+ @staticmethod
166
+ def parse_analyse_serialise(pad: dict, optimise: boolean, name:str):
167
+ import tuning
168
+ from flightanalysis import enable_logging
169
+ logger = enable_logging("INFO")
170
+ logger.info(f"Running {name}")
171
+ try:
172
+ pad = Scored.from_dict(pad)
173
+ except Exception as e:
174
+ logger.exception(f"Failed to parse {pad['id']}")
175
+ return pad
176
+
177
+ try:
178
+ pad = pad.proceed().run_all(optimise)
179
+ logger.info(f"Completed {name}")
180
+ return pad.to_dict()
181
+ except Exception as e:
182
+ logger.exception(f"Failed to process {name}")
183
+ return pad.to_dict()
136
184
 
137
185
  from .alignment import Alignment # noqa: E402
138
186
  from .complete import Complete # noqa: E402
@@ -1,56 +1,40 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
+ from functools import partial
5
+ from numbers import Number
4
6
 
5
7
  import geometry as g
6
8
  import numpy as np
9
+ import pandas as pd
7
10
  from flightdata import State
8
11
  from loguru import logger
12
+ from joblib import Parallel, delayed
9
13
 
10
14
  from flightanalysis.definition import ElDef, ManDef
11
15
  from flightanalysis.elements import Element
12
- from flightanalysis.manoeuvre import Manoeuvre
13
16
  from flightanalysis.scoring import (
14
17
  ElementsResults,
15
18
  ManoeuvreResults,
16
19
  Results,
17
20
  )
18
-
21
+ import os
19
22
  from ..el_analysis import ElementAnalysis
20
23
  from .alignment import Alignment
21
24
  from .basic import Basic
22
25
 
23
26
 
24
- @dataclass
27
+ @dataclass(repr=False)
25
28
  class Complete(Alignment):
26
- corrected: Manoeuvre
27
- corrected_template: State
29
+ # corrected: Manoeuvre
30
+ # corrected_templates: dict[str, State]
28
31
 
29
32
  @staticmethod
30
33
  def from_dict(ajman: dict) -> Complete | Alignment | Basic:
31
- analysis = Alignment.from_dict(ajman)
32
- if (
33
- isinstance(analysis, Alignment)
34
- and ajman["corrected"]
35
- and ajman["corrected_template"]
36
- ):
37
- return Complete(
38
- **analysis.__dict__,
39
- corrected=Manoeuvre.from_dict(ajman["corrected"]),
40
- corrected_template=State.from_dict(ajman["corrected_template"]),
41
- )
42
- else:
43
- return analysis
44
-
45
- def to_dict(self, basic: bool=False) -> dict:
46
- _basic = super().to_dict(basic)
47
- if basic:
48
- return _basic
49
- return dict(
50
- **_basic,
51
- corrected=self.corrected.to_dict(),
52
- corrected_template=self.corrected_template.to_dict(),
53
- )
34
+ return Alignment.from_dict(ajman).proceed()
35
+
36
+ def to_dict(self, basic: bool = False) -> dict:
37
+ return super().to_dict(basic)
54
38
 
55
39
  def run(self, optimise_aligment=True) -> Scored:
56
40
  if optimise_aligment:
@@ -70,7 +54,10 @@ class Complete(Alignment):
70
54
  yield self.get_ea(edn)
71
55
 
72
56
  def __getitem__(self, i):
73
- return self.get_ea(self.mdef.eds[i + 1].name)
57
+ if isinstance(i, Number):
58
+ return self.get_ea(self.mdef.eds[i + 1].name)
59
+ else:
60
+ return self.get_ea(i)
74
61
 
75
62
  def __getattr__(self, name):
76
63
  if name in self.mdef.eds.data.keys():
@@ -83,7 +70,7 @@ class Complete(Alignment):
83
70
  def get_ea(self, name):
84
71
  el: Element = getattr(self.manoeuvre.all_elements(), name)
85
72
  st = el.get_data(self.flown)
86
- tp = el.get_data(self.template).relocate(st.pos[0])
73
+ tp = self.templates[el.uid].relocate(st.pos[0])
87
74
 
88
75
  return ElementAnalysis(
89
76
  self.get_edef(name), self.mdef.mps, el, st, tp, el.ref_frame(tp)
@@ -100,8 +87,8 @@ class Complete(Alignment):
100
87
  self.mdef.info,
101
88
  self.mdef.mps.update_defaults(self.manoeuvre),
102
89
  self.mdef.eds,
90
+ self.mdef.box,
103
91
  )
104
- correction = mdef.create().add_lines()
105
92
 
106
93
  return Complete(
107
94
  self.id,
@@ -110,8 +97,6 @@ class Complete(Alignment):
110
97
  mdef,
111
98
  manoeuvre,
112
99
  template,
113
- correction,
114
- correction.create_template(template[0], self.flown),
115
100
  )
116
101
  else:
117
102
  return self
@@ -129,9 +114,9 @@ class Complete(Alignment):
129
114
  ) -> int:
130
115
  el1: Element = self.manoeuvre.all_elements()[eln1]
131
116
  el2: Element = self.manoeuvre.all_elements()[eln2]
132
-
117
+ min_len = 3
133
118
  def score_split(steps: int) -> float:
134
- new_fl = fl.shift_label(steps, 2, manoeuvre=self.name, element=eln1)
119
+ new_fl = fl.step_label("element", eln1, steps, fl.t, min_len)
135
120
  res1, new_iatt = self.get_score(eln1, itrans, el1.get_data(new_fl))
136
121
 
137
122
  el2fl = el2.get_data(new_fl)
@@ -145,9 +130,17 @@ class Complete(Alignment):
145
130
  return res1.total + res2.total
146
131
 
147
132
  dgs = {0: score_split(0)}
133
+
134
+ def check_steps(stps: int):
135
+ return not ((stps > 0 and len(el2.get_data(fl)) <= stps + min_len) or (
136
+ stps < 0 and len(el1.get_data(fl)) <= -stps + min_len
137
+ ))
148
138
 
149
139
  steps = int(len(el1.get_data(fl)) > len(el2.get_data(fl))) * 2 - 1
150
-
140
+
141
+ if not check_steps(steps):
142
+ return 0
143
+
151
144
  new_dg = score_split(steps)
152
145
  if new_dg > dgs[0]:
153
146
  steps = -steps
@@ -155,12 +148,11 @@ class Complete(Alignment):
155
148
  steps += np.sign(steps)
156
149
  dgs[steps] = new_dg
157
150
 
158
- while True:
159
- if (steps > 0 and len(el2.get_data(fl)) <= steps + 3) or (
160
- steps < 0 and len(el1.get_data(fl)) <= -steps + 3
161
- ):
151
+ while check_steps(steps):
152
+ try:
153
+ new_dg = score_split(steps)
154
+ except ValueError:
162
155
  break
163
- new_dg = score_split(steps)
164
156
 
165
157
  if new_dg < list(dgs.values())[-1]:
166
158
  dgs[steps] = new_dg
@@ -194,8 +186,8 @@ class Complete(Alignment):
194
186
  f"Adjusting split between {eln1} and {eln2} by {steps} steps"
195
187
  )
196
188
 
197
- fl = fl.shift_label(steps, 2, manoeuvre=self.name, element=eln1)
198
-
189
+ # fl = fl.shift_label(steps, 2, manoeuvre=self.name, element=eln1)
190
+ fl = fl.step_label("element", eln1, steps, fl.t, 3)
199
191
  adjusted.update([eln1, eln2])
200
192
 
201
193
  padjusted = adjusted
@@ -206,6 +198,9 @@ class Complete(Alignment):
206
198
 
207
199
  return Basic(self.id, self.schedule_direction, fl, self.mdef).proceed()
208
200
 
201
+ def optimise_alignment_v2(self):
202
+ pass
203
+
209
204
  def intra(self):
210
205
  return ElementsResults([ea.intra_score() for ea in self])
211
206
 
@@ -216,10 +211,55 @@ class Complete(Alignment):
216
211
  return self.mdef.box.score(self.mdef.info, self.flown, self.template)
217
212
 
218
213
  def plot_3d(self, **kwargs):
219
- from plotting import plotdtw, plotsec
214
+ from plotting import plotsec
220
215
 
221
- fig = plotdtw(self.flown, self.flown.data.element.unique())
216
+ fig = self.flown.plotlabels("element")
222
217
  return plotsec(self.flown, color="blue", nmodels=20, fig=fig, **kwargs)
223
218
 
219
+ def set_boundaries(self, boundaries: list[float]):
220
+ new_man = Basic(
221
+ self.id,
222
+ self.schedule_direction,
223
+ self.flown.set_boundaries("element", boundaries),#.resample(),
224
+ self.mdef,
225
+ ).proceed()
226
+
227
+ return new_man.run_all(False) if isinstance(self, Scored) else new_man
228
+
229
+ def set_boundary(self, el: str | int, boundary: float):
230
+ # TODO check if boundary is within bounds
231
+ boundaries = self.flown.labels.element.boundaries
232
+ elid = el if isinstance(el, int) else self.elnames.index(el)
233
+ boundaries[elid] = boundary
234
+ return self.set_boundaries(boundaries)
235
+
236
+ def boundary_sweep(self, el: str | int, width: float, substeps: int = 5):
237
+ """Sweep an element boundary through a width and return a set of results
238
+ width is in seconds,
239
+ substeps is the number of steps to take within each timestep
240
+ TODO make sure range doesn't cross adjacent boundary
241
+ """
242
+ elid = el if isinstance(el, int) else self.elnames.index(el)
243
+ bopt = self.flown.labels.element.boundaries[elid]
244
+ tstart = bopt - width
245
+ tstop = bopt + width
246
+ ts = self.flown.data.t[tstart:tstop].to_numpy()
247
+ splits = np.array(
248
+ [
249
+ t0 + (t1 - t0) * i / substeps
250
+ for t0, t1 in zip(ts[:-1], ts[1:])
251
+ for i in range(substeps)
252
+ ]
253
+ )
254
+ logger.info(f"Starting {os.cpu_count() * 2 - 1} processes to run {len(splits)} manoeuvres")
255
+ madicts = Parallel(n_jobs=os.cpu_count() * 2 - 1)(
256
+ delayed(partial(Basic.parse_analyse_serialise, optimise=False, name=i))(self.set_boundary(el, ic).to_dict())
257
+ for i, ic in enumerate(splits)
258
+ )
259
+ outdata = {ic: Scored.from_dict(mad).scores.dg_dict() for ic, mad in zip(splits, madicts) }
260
+
261
+ return pd.DataFrame(outdata).T
262
+
263
+
224
264
 
225
265
  from .scored import Scored # noqa: E402
@@ -4,7 +4,7 @@ from flightanalysis.scoring import ManoeuvreResults
4
4
  from .complete import Complete
5
5
 
6
6
 
7
- @dataclass
7
+ @dataclass(repr=False)
8
8
  class Scored(Complete):
9
9
  scores: ManoeuvreResults
10
10
 
@@ -16,14 +16,16 @@ class Scored(Complete):
16
16
  self.mdef,
17
17
  self.manoeuvre,
18
18
  self.template,
19
- self.corrected,
20
- self.corrected_template,
21
19
  )
22
20
 
23
21
  @staticmethod
24
22
  def from_dict(ajman: dict) -> Scored:
25
23
  analysis = Complete.from_dict(ajman)
26
- if isinstance(analysis, Complete) and ajman["scores"]:
24
+ if (
25
+ isinstance(analysis, Complete)
26
+ and "scores" in ajman
27
+ and ajman["scores"] is not None
28
+ ):
27
29
  return Scored(
28
30
  **analysis.__dict__, scores=ManoeuvreResults.from_dict(ajman["scores"])
29
31
  )
@@ -39,4 +41,10 @@ class Scored(Complete):
39
41
  return dict(**_basic, scores=self.scores.to_dict())
40
42
 
41
43
  def fcj_results(self):
42
- return dict(**super().fcj_results(), results=self.scores.fcj_results())
44
+ return dict(
45
+ els=[
46
+ dict(name=k, start=v.start, stop=v.stop)
47
+ for k, v in self.flown.labels.element.labels.items()
48
+ ],
49
+ results=self.scores.fcj_results(),
50
+ )