flightanalysis 0.4.0__tar.gz → 0.4.1__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 (134) hide show
  1. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/PKG-INFO +2 -2
  2. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/pyproject.toml +2 -2
  3. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/__init__.py +0 -15
  4. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/el_analysis.py +13 -3
  5. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/manoeuvre_analysis.py +171 -56
  6. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/schedule_analysis.py +24 -23
  7. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/base/ref_funcs.py +1 -2
  8. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/base/utils.py +28 -13
  9. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/builders/elbuilders.py +2 -1
  10. flightanalysis-0.4.1/src/flightanalysis/builders/element_descriptions.py +111 -0
  11. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/builders/manbuilder.py +5 -4
  12. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/__init__.py +6 -2
  13. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/collectors.py +2 -3
  14. flightanalysis-0.4.1/src/flightanalysis/definition/combo_setting.py +32 -0
  15. flightanalysis-0.4.1/src/flightanalysis/definition/combo_settings.py +118 -0
  16. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/eldef.py +3 -4
  17. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/mandef.py +27 -14
  18. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/manparm.py +4 -158
  19. flightanalysis-0.4.1/src/flightanalysis/definition/manparms.py +249 -0
  20. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/operation.py +5 -0
  21. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/scheddef.py +4 -5
  22. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/__init__.py +2 -0
  23. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/element.py +26 -3
  24. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/spin.py +9 -5
  25. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/tags.py +19 -1
  26. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/manoeuvre.py +25 -45
  27. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/schedule.py +1 -2
  28. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/box.py +13 -12
  29. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/parser.py +1 -1
  30. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/__init__.py +3 -0
  31. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/criteria.py +9 -4
  32. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/criteria_group.py +1 -2
  33. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/exponential.py +10 -7
  34. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/bounded.py +25 -18
  35. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/continuous.py +3 -3
  36. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/deviation.py +5 -4
  37. flightanalysis-0.4.1/src/flightanalysis/scoring/criteria/intra/peak.py +82 -0
  38. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/single.py +3 -3
  39. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/parser.py +2 -0
  40. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/downgrade/base.py +5 -1
  41. flightanalysis-0.4.1/src/flightanalysis/scoring/downgrade/dg_testing.py +39 -0
  42. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/downgrade/downgrade.py +57 -15
  43. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/downgrade/downgrade_pair.py +2 -1
  44. flightanalysis-0.4.1/src/flightanalysis/scoring/downgrade/downgrades.py +74 -0
  45. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/dgplot.py +0 -7
  46. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/elements_results.py +1 -3
  47. flightanalysis-0.4.1/src/flightanalysis/scoring/results/result.py +255 -0
  48. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/results.py +31 -34
  49. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results.py +2 -6
  50. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/visibility.py +10 -3
  51. flightanalysis-0.4.1/tests/data/fcs_csv.csv +4 -0
  52. flightanalysis-0.4.1/tests/test_manparms.py +102 -0
  53. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_operations.py +2 -1
  54. flightanalysis-0.4.1/tests/test_scoring/test_criteria_bounded.py +82 -0
  55. flightanalysis-0.4.1/tests/test_scoring/test_criteria_continuous.py +82 -0
  56. flightanalysis-0.4.1/tests/test_scoring/test_criteria_peak.py +37 -0
  57. flightanalysis-0.4.1/tests/test_scoring/test_criteria_single.py +28 -0
  58. flightanalysis-0.4.1/tests/test_scoring/test_inter_criteria.py +54 -0
  59. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_utils.py +9 -1
  60. flightanalysis-0.4.0/src/flightanalysis/analysis/aligment_optimisation.py +0 -171
  61. flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/intra/peak.py +0 -72
  62. flightanalysis-0.4.0/src/flightanalysis/scoring/downgrade/downgrades.py +0 -58
  63. flightanalysis-0.4.0/src/flightanalysis/scoring/results/result.py +0 -312
  64. flightanalysis-0.4.0/tests/data/fcs_csv.csv +0 -3
  65. flightanalysis-0.4.0/tests/test_scoring/__init__.py +0 -0
  66. flightanalysis-0.4.0/tests/test_scoring/test_criteria.py +0 -218
  67. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/.github/workflows/publish_pypi.yml +0 -0
  68. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/.gitignore +0 -0
  69. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/LICENSE +0 -0
  70. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/README.md +0 -0
  71. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/__init__.py +0 -0
  72. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/criteria.ipynb +0 -0
  73. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/making_elements.py +0 -0
  74. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/manoeuvre_definition.py +0 -0
  75. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/reversing_spin.py +0 -0
  76. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/tailslide.py +0 -0
  77. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/__init__.py +0 -0
  78. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/builders/__init__.py +0 -0
  79. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/manoption.py +0 -0
  80. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/__init__.py +0 -0
  81. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/funopp.py +0 -0
  82. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/itemopp.py +0 -0
  83. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/mathopp.py +0 -0
  84. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/sumopp.py +0 -0
  85. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/line.py +0 -0
  86. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/loop.py +0 -0
  87. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/snap.py +0 -0
  88. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/stall_turn.py +0 -0
  89. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/tail_slide.py +0 -0
  90. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/py.typed +0 -0
  91. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/__init__.py +0 -0
  92. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/__init__.py +0 -0
  93. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/rectangular_box.py +0 -0
  94. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/triangular_box.py +0 -0
  95. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/inter/__init__.py +0 -0
  96. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/inter/combination.py +0 -0
  97. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/inter/comparison.py +0 -0
  98. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/__init__.py +0 -0
  99. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/downgrade/__init__.py +0 -0
  100. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/measurement.py +0 -0
  101. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/reffuncs.py +0 -0
  102. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/__init__.py +0 -0
  103. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/manoeuvre_results.py +0 -0
  104. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/summary.py +0 -0
  105. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/version.py +0 -0
  106. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/EmailedBox.f3a +0 -0
  107. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/__init__.py +0 -0
  108. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/conftest.py +0 -0
  109. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/manual_F3A_P23.json +0 -0
  110. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/old_json.json +0 -0
  111. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/p23.BIN +0 -0
  112. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/p23_box.f3a +0 -0
  113. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/p23_fc.json +0 -0
  114. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/p23_flight.json +0 -0
  115. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/scored_fcj.json +0 -0
  116. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/unscored_fcj.json +0 -0
  117. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_rfuncs.py +0 -0
  118. {flightanalysis-0.4.0/tests/test_definition → flightanalysis-0.4.1/tests/test_schedule}/__init__.py +0 -0
  119. {flightanalysis-0.4.0/tests/test_schedule → flightanalysis-0.4.1/tests/test_schedule/test_element}/__init__.py +0 -0
  120. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/loop_analysis.json +0 -0
  121. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/p23_th_e0.csv +0 -0
  122. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/p23_th_e0.json +0 -0
  123. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/p23_th_e0_template.csv +0 -0
  124. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element.py +0 -0
  125. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_line.py +0 -0
  126. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_loop.py +0 -0
  127. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_snap.py +0 -0
  128. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_spin.py +0 -0
  129. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_stallturn.py +0 -0
  130. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_manoeuvre.py +0 -0
  131. {flightanalysis-0.4.0/tests/test_schedule/test_element → flightanalysis-0.4.1/tests/test_scoring}/__init__.py +0 -0
  132. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_scoring/test_box.py +0 -0
  133. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_scoring/test_lookup.py +0 -0
  134. {flightanalysis-0.4.0 → flightanalysis-0.4.1}/uv.lock +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flightanalysis
3
- Version: 0.4.0
3
+ Version: 0.4.1
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.3.5
9
+ Requires-Dist: flightdata>=0.3.6
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
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "flightanalysis"
3
- version="0.4.0"
3
+ version="0.4.1"
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.3.5",
10
+ "flightdata>=0.3.6",
11
11
  "joblib>=1.4.2",
12
12
  "json-stream>=2.3.2",
13
13
  "loguru>=0.7.2",
@@ -5,18 +5,3 @@ from .schedule import Schedule # noqa: F401
5
5
  from .definition import * # noqa: F403
6
6
  from .scoring import * # noqa: F403
7
7
  from .analysis import ScheduleAnalysis, ElementAnalysis, Analysis # noqa: F401
8
-
9
- import sys
10
- from loguru import logger
11
-
12
- logger.disable('flightanalysis')
13
-
14
- def enable_logging(level: str = 'INFO'):
15
- logger.enable('flightanalysis')
16
- logger.remove()
17
- logger.add(
18
- sys.stderr,
19
- level=level
20
- )
21
- return logger
22
-
@@ -1,13 +1,14 @@
1
1
  from __future__ import annotations
2
+ from flightanalysis.definition.manparms import ManParms
2
3
  import numpy as np
3
4
 
4
5
  from flightanalysis.scoring.results import Results, Result
5
6
  from flightdata import State
6
7
  from typing import Self
7
- from flightanalysis import ElDef, Element, ManParms
8
+ from flightanalysis import ElDef, Element, Elements
8
9
  from dataclasses import dataclass
9
10
  import geometry as g
10
-
11
+ from flightanalysis.scoring.downgrade.dg_testing import DGTest
11
12
 
12
13
  @dataclass
13
14
  class ElementAnalysis:
@@ -47,7 +48,7 @@ class ElementAnalysis:
47
48
  )
48
49
 
49
50
  def score_dg(self, dg: str, limits: bool = True) -> Result:
50
- return self.edef.dgs[dg](self.el, self.fl, self.tp, limits)
51
+ return self.edef.dgs[dg](self.el, self.fl, self.tp)
51
52
 
52
53
  def intra_score(self) -> Results:
53
54
  try:
@@ -69,6 +70,15 @@ class ElementAnalysis:
69
70
  **(dict(intra=f"{self.results.total:.4f}") if self.results is not None else {}),
70
71
  ) # fmt: skip
71
72
 
73
+
74
+ def create_test_dataset(self, dg: str):
75
+ return DGTest(
76
+ self.edef.dgs[dg],
77
+ Elements([self.el]),
78
+ self.fl,
79
+ self.tp,
80
+ )
81
+
72
82
  def full_result_plot(self, name: str, showlegend=True, textposition: str = "bottom left"):
73
83
  """plot the actual result and the uncropped result on the same figure,
74
84
  include some info from the ddowngrade"""
@@ -1,14 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, replace
4
+ from json import dumps
4
5
  from typing import Annotated, Self, Literal
5
6
  from flightanalysis.definition import ManDef, ManOption
6
7
  from flightanalysis.elements import AnyElement
7
8
  from flightanalysis.manoeuvre import Manoeuvre
8
9
  from flightanalysis.analysis.el_analysis import ElementAnalysis
9
10
  from flightanalysis.scoring.results import ElementsResults, ManoeuvreResults
10
-
11
- from .aligment_optimisation import optimise_alignment
12
11
  import numpy as np
13
12
 
14
13
  import geometry as g
@@ -32,6 +31,7 @@ class Analysis:
32
31
  ]
33
32
  flown: State
34
33
  mdef: ManDef | ManOption
34
+ option: int | None = None
35
35
  itrans: g.Transformation | None = None
36
36
  manoeuvre: Manoeuvre | None = None
37
37
  templates: dict[str, State] | None = None
@@ -46,38 +46,49 @@ class Analysis:
46
46
  optimise: bool = True,
47
47
  throw_errors: bool = False,
48
48
  stop_after: str = None,
49
+ force: bool = True,
49
50
  **kwargs,
50
51
  ) -> Self:
51
- stages = []
52
- stages.append("create_itrans")
53
- if "element" in self.flown.labels.keys():
54
- stages.append("select_mdef")
55
- elif "element" not in self.flown.labels.keys():
56
- stages.extend(["preliminary_alignment", "secondary_alignment"])
57
- if optimise:
58
- stages.extend(["prepare_scoring", "optimise_alignment"])
59
- stages.extend(["prepare_scoring", "calculate_score"])
60
-
61
- for stage, fun in enumerate(stages):
62
- try:
63
- logger.debug(f"Running step {stage}: {fun}")
64
- self = getattr(self, fun)(**kwargs.get(fun, {}))
65
- if stop_after == fun:
66
- logger.debug(f"Stopping after step {stage}: {fun}")
67
- break
68
- except ElSequenceError as ese:
69
- logger.warning(f"{self.name}, {fun}: {ese}")
70
- self = self.preliminary_alignment(
71
- **kwargs.get("preliminary_alignment", {})
72
- )
73
- self = self.secondary_alignment(**kwargs.get("secondary_alignment", {}))
74
- except Exception as e:
75
- if throw_errors:
76
- raise Exception(f"Error running {self.name}, {fun}: {e}") from e
77
- else:
78
- logger.error(f"{self.name}, {fun}: {e}")
79
- break
80
-
52
+ if self.scores and not force:
53
+ logger.info(f"Analysis {self.id} already has scores, skipping run.")
54
+ return self
55
+ stages = [
56
+ ("create_itrans", True),
57
+ ("select_mdef", "element" in self.flown.labels.keys()),
58
+ ("preliminary_alignment", "element" not in self.flown.labels.keys()),
59
+ ("secondary_alignment", "element" not in self.flown.labels.keys()),
60
+ ("prepare_scoring", optimise),
61
+ ("optimise_alignment", optimise),
62
+ ("prepare_scoring", True),
63
+ ("calculate_score", True),
64
+ ]
65
+ for stage, (fun_name, run) in enumerate(stages):
66
+ if run:
67
+ try:
68
+ logger.debug(f"Running step {stage}: {fun_name}")
69
+ self = getattr(self, fun_name)(**kwargs.get(fun_name, {}))
70
+ if stop_after == fun_name:
71
+ logger.debug(f"Stopping after step {stage}: {fun_name}")
72
+ break
73
+ except ElSequenceError as ese:
74
+ if throw_errors:
75
+ raise ElSequenceError(
76
+ f"Error running {self.name}, {fun_name}: {ese}"
77
+ ) from ese
78
+ else:
79
+ logger.warning(f"{self.name}, {fun_name}: {ese}")
80
+ stages[2] = (stages[2][0], True)
81
+ stages[3] = (stages[3][0], True)
82
+ except Exception as e:
83
+ if throw_errors:
84
+ raise Exception(
85
+ f"Error running {self.name}, {fun_name}: {e}"
86
+ ) from e
87
+ else:
88
+ logger.error(f"{self.name}, {fun_name}: {e}")
89
+ break
90
+ else:
91
+ logger.debug(f"Skipping step {stage}: {fun_name}")
81
92
  return self
82
93
 
83
94
  def _create_itrans(self) -> g.Transformation:
@@ -105,6 +116,13 @@ class Analysis:
105
116
  """If the elements are labelled it should be possible to select the correct option.
106
117
  First find all the options that match the element sequence.
107
118
  If more than one option matches, select the one with the best score.
119
+
120
+ There is a problem if both options have the same element names
121
+ in this case we need to score both and choose the best.
122
+
123
+ NOTE (0.5.1 on) - added option index to the class, so if that exists use that.
124
+ If not then do the old check and set the option for next time.
125
+ This function can be simplified once all the old analyses have been updated with the option.
108
126
  """
109
127
  mopt = ManOption([self.mdef]) if isinstance(self.mdef, ManDef) else self.mdef
110
128
 
@@ -112,7 +130,7 @@ class Analysis:
112
130
 
113
131
  options = []
114
132
  for md in mopt:
115
- if len(elnames) == len(md.eds) + 1 and np.all(
133
+ if len(elnames) == len(md.eds) and np.all(
116
134
  [elnames[i] == k for i, k in enumerate(md.eds.data.keys())]
117
135
  ):
118
136
  options.append(md)
@@ -120,7 +138,8 @@ class Analysis:
120
138
  raise ElSequenceError(
121
139
  f"{self.mdef.info.short_name} element sequence doesn't agree with {elnames}"
122
140
  )
123
-
141
+ elif self.option is not None:
142
+ option_id = self.option
124
143
  elif len(options) == 1:
125
144
  option_id = 0
126
145
  else:
@@ -128,12 +147,12 @@ class Analysis:
128
147
  for option in options:
129
148
  scores.append(replace(self, mdef=option).run(False).scores.score())
130
149
  option_id = np.argmax(scores)
131
- return replace(self, mdef=options[option_id])
150
+ return replace(self, mdef=options[option_id], option=option_id)
132
151
 
133
152
  def _preliminary_alignment(
134
153
  self, mdef: ManDef, freq: int = 25, radius: AlignRadiusOption = 10
135
154
  ):
136
- manoeuvre = mdef.create().add_lines()
155
+ manoeuvre = mdef.create()
137
156
  templates = manoeuvre.create_template(self.itrans, None, freq, "min")
138
157
  template = State.stack(templates, "element")
139
158
  res = Alignment.align(self.flown, template, radius, True)
@@ -172,22 +191,24 @@ class Analysis:
172
191
  )
173
192
 
174
193
  def prepare_scoring(self) -> Self:
175
- manoeuvre = (self.manoeuvre or self.mdef.create().add_lines()).match_intention(
194
+ manoeuvre = (self.manoeuvre or self.mdef.create()).match_intention(
176
195
  self.itrans, self.flown, 0, "min", True
177
196
  )[0]
178
197
 
179
198
  mdef = self.mdef.update_defaults(manoeuvre)
180
- corrected = mdef.create().add_lines()
199
+ corrected = mdef.create()
181
200
 
182
201
  manoeuvre = manoeuvre.copy_directions(corrected)
183
202
  templates = manoeuvre.create_template(self.itrans, self.flown)
184
203
 
185
204
  return replace(self, mdef=mdef, manoeuvre=manoeuvre, templates=templates)
186
205
 
187
- def optimise_alignment(self):
188
- fl = optimise_alignment(self.flown, self.mdef, self.manoeuvre, self.templates)
189
-
190
- return replace(self, flown=fl)
206
+ def optimise_alignment(self, include_inter: bool = True) -> Self:
207
+ steps = {}
208
+ for el in list(self.manoeuvre.elements.keys())[:-1]:
209
+ steps[el], self = self.optimise_boundary(el, include_inter)
210
+ logger.debug(f"optimisation result:\n{dumps(steps, indent=2)}")
211
+ return self
191
212
 
192
213
  def intra(self):
193
214
  return ElementsResults([ea.intra_score() for ea in self])
@@ -196,7 +217,9 @@ class Analysis:
196
217
  return self.mdef.mps.collect(self.manoeuvre, self.flown, self.mdef.box)
197
218
 
198
219
  def positioning(self):
199
- return self.mdef.box.score(self.mdef.info, self.flown, self.template)
220
+ return self.mdef.box.score(
221
+ self.mdef.info, self.manoeuvre.elements, self.flown, self.template
222
+ )
200
223
 
201
224
  def calculate_score(self) -> Self:
202
225
  def fun(group: Literal["inter", "intra", "positioning"]):
@@ -210,6 +233,97 @@ class Analysis:
210
233
  scores=ManoeuvreResults(fun("inter"), fun("intra"), fun("positioning")),
211
234
  )
212
235
 
236
+ def reload_element(self, name: str | int) -> Self:
237
+ ist = self.templates[name][0].relocate(self.flown.element[name][0].pos)
238
+ new_man = self.manoeuvre.replace_elements(
239
+ **{
240
+ name: self.manoeuvre.elements[name].match_intention(
241
+ ist, self.flown.element[name]
242
+ )
243
+ }
244
+ )
245
+ new_templates = self.templates | {
246
+ name: new_man.elements[name].create_template(ist, self.flown.element[name])
247
+ }
248
+
249
+ return replace(
250
+ self,
251
+ mdef=replace(self.mdef, mps=self.mdef.mps.update_defaults(new_man)),
252
+ manoeuvre=new_man,
253
+ templates=new_templates,
254
+ scores=None,
255
+ )
256
+
257
+ def update_boundaries(self, new_fl: State) -> Self:
258
+ changes = [
259
+ k
260
+ for k in self.manoeuvre.elements.keys()
261
+ if new_fl.labels.element[k] != self.flown.labels.element[k]
262
+ ]
263
+ _new: Self = replace(self, flown=new_fl)
264
+ for change in changes:
265
+ _new = _new.reload_element(change)
266
+ return _new
267
+
268
+ def shift_boundary(self, boundary: str, t: float) -> Self:
269
+ return self.update_boundaries(
270
+ self.flown.move_label(
271
+ "element", boundary, self.flown.labels.element[boundary].stop + t
272
+ )
273
+ )
274
+
275
+ def step_boundary(self, boundary: str, steps: int) -> Self:
276
+ return self.update_boundaries(
277
+ self.flown.step_label("element", boundary, steps, self.flown.t, 3)
278
+ )
279
+
280
+ def score_boundary(self, boundary: str, include_inter: bool = True):
281
+ next_el = self.manoeuvre.elnames[self.manoeuvre.elnames.index(boundary) + 1]
282
+ return ElementsResults(
283
+ {
284
+ boundary: self[boundary].intra_score(),
285
+ next_el: self[next_el].intra_score(),
286
+ **({"inter": self.inter()} if include_inter else {}),
287
+ }
288
+ )
289
+
290
+ def optimise_boundary(
291
+ self, boundary: str, include_inter: bool = True
292
+ ) -> list[Self | int]:
293
+ next_el = self.manoeuvre.elnames[self.manoeuvre.elnames.index(boundary) + 1]
294
+
295
+ def score_step(_steps: int):
296
+ try:
297
+ _new = self.step_boundary(boundary, _steps)
298
+ _results = _new.score_boundary(boundary, include_inter)
299
+ return [_results.total, _new]
300
+ except Exception as _:
301
+ return None
302
+
303
+ best = score_step(0)
304
+ direction = (
305
+ -1
306
+ if self.flown.labels.element[boundary].width
307
+ > self.flown.labels.element[next_el].width
308
+ else 1
309
+ )
310
+
311
+ _check = score_step(direction)
312
+ if _check is not None and _check[0] < best[0]:
313
+ best = _check
314
+ steps = direction
315
+ else:
316
+ direction = -direction
317
+ steps = 0
318
+
319
+ while True:
320
+ steps += direction
321
+ _check = score_step(steps)
322
+ if _check is not None and _check[0] < best[0]:
323
+ best = _check
324
+ else:
325
+ return steps, best[1]
326
+
213
327
  def get_ea(self, name: str | int) -> ElementAnalysis:
214
328
  el: AnyElement = self.manoeuvre.elements[name]
215
329
  fl = self.flown.element[el.uid]
@@ -224,7 +338,7 @@ class Analysis:
224
338
  self.scores.intra[name] if self.scores else None,
225
339
  )
226
340
 
227
- def __getattr__(self, name: str):
341
+ def __getattr__(self, name: str) -> ElementAnalysis:
228
342
  if name in self.flown.labels.element.keys():
229
343
  return self.get_ea(name)
230
344
  raise AttributeError(f"{self.__class__.__name__} has no attribute {name}")
@@ -272,22 +386,21 @@ class Analysis:
272
386
 
273
387
  @staticmethod
274
388
  def from_dict(data: dict):
275
- if "manoeuvre" in data and data["manoeuvre"]:
276
- manoeuvre = Manoeuvre.from_dict(data["manoeuvre"])
277
- else:
278
- manoeuvre = None
389
+ manoeuvre = (
390
+ Manoeuvre.from_dict(data["manoeuvre"]) if data.get("manoeuvre") else None
391
+ )
279
392
 
280
- if "templates" in data and data["templates"]:
281
- templates = {k: State.from_dict(v) for k, v in data["templates"].items()}
282
- else:
283
- templates = None
393
+ templates = (
394
+ {k: State.from_dict(v) for k, v in data["templates"].items()}
395
+ if data.get("templates")
396
+ else None
397
+ )
284
398
 
285
399
  itrans = list(templates.values())[0][0].transform if templates else None
286
400
 
287
- if "scores" in data and data["scores"]:
288
- scores = ManoeuvreResults.from_dict(data["scores"])
289
- else:
290
- scores = None
401
+ scores = (
402
+ ManoeuvreResults.from_dict(data["scores"]) if data.get("scores") else None
403
+ )
291
404
 
292
405
  return Analysis(
293
406
  data["id"],
@@ -296,6 +409,7 @@ class Analysis:
296
409
  else None,
297
410
  State.from_dict(data["flown"]),
298
411
  ManDef.from_dict(data["mdef"]),
412
+ data.get("option"),
299
413
  itrans,
300
414
  manoeuvre,
301
415
  templates,
@@ -309,6 +423,7 @@ class Analysis:
309
423
  if self.schedule_direction
310
424
  else None,
311
425
  flown=self.flown.to_dict(True),
426
+ option=self.option,
312
427
  **(
313
428
  {}
314
429
  if basic
@@ -13,8 +13,7 @@ import numpy as np
13
13
  import pandas as pd
14
14
 
15
15
 
16
- class ScheduleAnalysis(Collection):
17
- VType = Analysis
16
+ class ScheduleAnalysis(Collection[Analysis]):
18
17
  uid = "name"
19
18
 
20
19
  @property
@@ -25,9 +24,6 @@ class ScheduleAnalysis(Collection):
25
24
  def template(self):
26
25
  return State.stack([m.template for m in self])
27
26
 
28
- def proceed(self):
29
- return ScheduleAnalysis([m.proceed() for m in self])
30
-
31
27
  @staticmethod
32
28
  def parse_ajson(ajson: AJson, sdef: SchedDef = None) -> ScheduleAnalysis:
33
29
  if not all([ajson.mdef for ajson in ajson.mans]) and sdef is None:
@@ -36,11 +32,17 @@ class ScheduleAnalysis(Collection):
36
32
  if sdef is None:
37
33
  sdef = [ManDef.from_dict(man.mdef) for man in ajson.mans]
38
34
  for man, mdef in zip(ajson.mans, sdef):
39
- analyses.append(Analysis.from_dict(man.model_dump() | dict(mdef=mdef.to_dict())))
35
+ analyses.append(
36
+ Analysis.from_dict(man.model_dump() | dict(mdef=mdef.to_dict()))
37
+ )
40
38
  return ScheduleAnalysis(analyses)
41
39
 
42
40
  def run(
43
- self, optimise: bool = False, sync: boolean = False, throw_errors: bool = False, subset: list = None
41
+ self,
42
+ optimise: bool = False,
43
+ sync: boolean = False,
44
+ throw_errors: bool = False,
45
+ subset: list = None,
44
46
  ) -> Self:
45
47
  if subset is None:
46
48
  subset = range(len(self))
@@ -54,41 +56,37 @@ class ScheduleAnalysis(Collection):
54
56
  ]
55
57
  else:
56
58
  madicts = Parallel(n_jobs=os.cpu_count() * 2 - 1)(
57
- delayed(Analysis.parse_analyse_serialise)(man.to_dict(), optimise, throw_errors)
59
+ delayed(Analysis.parse_analyse_serialise)(
60
+ man.to_dict(), optimise, throw_errors
61
+ )
58
62
  for i, man in enumerate(self)
59
63
  if i in subset
60
64
  )
61
65
 
62
66
  return ScheduleAnalysis(
63
67
  [
64
- Analysis.from_dict(madicts[subset.index(i)])
65
- if i in subset
66
- else self[i]
68
+ Analysis.from_dict(madicts[subset.index(i)]) if i in subset else self[i]
67
69
  for i in range(len(self))
68
70
  ]
69
71
  )
70
72
 
71
-
72
73
  def scores(self):
73
74
  scores = {}
74
75
  total = 0
75
76
  scores = {
76
- ma.name: (ma.scores.score() if hasattr(ma, "scores") and ma.scores else 0) for ma in self
77
+ ma.name: (ma.scores.score() if hasattr(ma, "scores") and ma.scores else 0)
78
+ for ma in self
77
79
  }
78
80
  total = sum([ma.mdef.info.k * v for ma, v in zip(self, scores.values())])
79
81
  return total, scores
80
82
 
81
83
  def summarydf(self):
82
- return pd.DataFrame(
83
- [m.scores.summary() if m.scores else {} for m in self]
84
- )
84
+ return pd.DataFrame([m.scores.summary() if m.scores else {} for m in self])
85
85
 
86
86
  def score_summary_df(self, difficulty=3, truncate=False):
87
87
  return pd.DataFrame(
88
88
  [
89
- ma.scores.score_summary(difficulty, truncate)
90
- if ma.scores
91
- else {}
89
+ ma.scores.score_summary(difficulty, truncate) if ma.scores else {}
92
90
  for ma in self
93
91
  ]
94
92
  )
@@ -96,7 +94,10 @@ class ScheduleAnalysis(Collection):
96
94
  def basic(self, sdef: SchedDef = None, remove_labels: bool = True) -> Self:
97
95
  return ScheduleAnalysis(
98
96
  [
99
- man.basic(sdef[man.mdef.info.short_name] if sdef is not None else None, remove_labels)
97
+ man.basic(
98
+ sdef[man.mdef.info.short_name] if sdef is not None else None,
99
+ remove_labels,
100
+ )
100
101
  for man in self
101
102
  ]
102
103
  )
@@ -104,11 +105,11 @@ class ScheduleAnalysis(Collection):
104
105
  @property
105
106
  def mnames(self):
106
107
  return [m.name for m in self]
107
-
108
+
108
109
  @property
109
110
  def fls(self):
110
111
  return {m.name: m.flown for m in self}
111
-
112
+
112
113
  @property
113
114
  def tps(self):
114
- return {m.name: m.template for m in self}
115
+ return {m.name: m.template for m in self}
@@ -96,8 +96,7 @@ class RefFunc:
96
96
  return description
97
97
 
98
98
 
99
- class RefFuncs(Collection):
100
- VType = RefFunc
99
+ class RefFuncs(Collection[RefFunc]):
101
100
  uid = "name"
102
101
 
103
102
  def to_list(self):
@@ -5,6 +5,7 @@ import pandas as pd
5
5
  from pathlib import Path
6
6
  import numpy as np
7
7
  from typing import Any
8
+ import re
8
9
 
9
10
 
10
11
  def combine_args(names: list[str], *args, **kwargs) -> dict:
@@ -43,10 +44,12 @@ def df_insert(df: pd.DataFrame, **kwargs) -> pd.DataFrame:
43
44
 
44
45
  def tryval(val):
45
46
  try:
46
- if val[-1] == "°":
47
- return np.radians(float(val[:-1]))
48
- else:
49
- return float(val)
47
+ hasdeg = val.find("°")
48
+ val = float(val.split("°")[0].strip())
49
+ if hasdeg >= 0:
50
+ val = np.radians(val)
51
+ return val
52
+
50
53
  except Exception:
51
54
  return val if len(val) else None
52
55
 
@@ -54,24 +57,27 @@ def tryval(val):
54
57
  def process_series(ser: pd.Series):
55
58
  if sum(ser == "True") + sum(ser == "False") == len(ser):
56
59
  return ser == "True"
57
- elif ser.dtype == object:
60
+ elif ser.dtype == object or ser.dtype == "string":
61
+
58
62
  try:
59
- deglocs = ser.str.endswith("°")
60
- if sum(deglocs):
61
- vals = ser.str.rstrip("°").astype(float)
62
- return np.where(deglocs, np.radians(vals), vals)
63
- else:
64
- return ser.astype(float)
63
+ vals = ser.str.extract(r'^([\d+]*\.?[\d]+)').iloc[:,0].astype(np.float64)
64
+ if np.isnan(vals).any():
65
+ return ser.values
66
+ deglocs = ser.str.contains("°")
67
+ if deglocs.any():
68
+ pass
69
+ return np.where(deglocs, np.radians(vals), vals)
70
+
65
71
  except Exception:
66
72
  return ser
67
73
  else:
68
- return ser
74
+ return ser.values
69
75
 
70
76
 
71
77
  def parse_csv(file: Path | str | pd.DataFrame, **kwargs) -> pd.DataFrame:
72
78
  path = Path(file)
73
79
  df: pd.DataFrame = pd.read_csv(path, **({"comment": "#"} | kwargs), keep_default_na=False).apply(
74
- lambda x: x.str.strip() if x.dtype == object else x
80
+ lambda x: x.str.strip() if x.dtype == object or x.dtype == "string" else x
75
81
  )
76
82
  df.columns = [c.strip() for c in df.columns]
77
83
  return df.apply(process_series)
@@ -105,3 +111,12 @@ def replace_any_depth_value(d: Any, old_value: Any, new_value: Any) -> dict:
105
111
  return new_value
106
112
  else:
107
113
  return d
114
+
115
+
116
+ def display_unit(value: Number, unit: str="", precision: int = 2) -> str:
117
+ new_unit = re.sub(r"radians|radian|rad", "°", unit)
118
+ if unit.find("rad") >= 0:
119
+ val = f"{np.degrees(value):.{precision}f}"
120
+ else:
121
+ val = f"{value:.{precision}f}"
122
+ return f"{val} {new_unit}"
@@ -1,11 +1,12 @@
1
1
  from numbers import Number
2
2
 
3
+ from flightanalysis.definition.manparms import ManParms
3
4
  import numpy as np
4
5
  import pandas as pd
5
6
 
6
7
  from flightanalysis.definition import ItemOpp, Opp, maxopp
7
8
  from flightanalysis.definition.collectors import Collectors
8
- from flightanalysis.definition.eldef import ElDef, ElDefs, ManParm, ManParms
9
+ from flightanalysis.definition.eldef import ElDef, ElDefs, ManParm
9
10
  from flightanalysis.elements import Line, Loop, Snap, Spin, StallTurn, TailSlide
10
11
  from flightanalysis.scoring import inter_visors as visors
11
12
  from flightanalysis.scoring.criteria.inter.comparison import free_comparison