flightanalysis 0.3.20__tar.gz → 0.4.0__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.
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/PKG-INFO +3 -3
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/pyproject.toml +3 -3
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/__init__.py +1 -1
- flightanalysis-0.4.0/src/flightanalysis/analysis/__init__.py +3 -0
- flightanalysis-0.4.0/src/flightanalysis/analysis/aligment_optimisation.py +171 -0
- flightanalysis-0.4.0/src/flightanalysis/analysis/el_analysis.py +338 -0
- flightanalysis-0.4.0/src/flightanalysis/analysis/manoeuvre_analysis.py +340 -0
- flightanalysis-0.3.20/src/flightanalysis/analysis/sch_analysis.py → flightanalysis-0.4.0/src/flightanalysis/analysis/schedule_analysis.py +14 -47
- flightanalysis-0.4.0/src/flightanalysis/base/ref_funcs.py +154 -0
- flightanalysis-0.4.0/src/flightanalysis/base/utils.py +107 -0
- flightanalysis-0.4.0/src/flightanalysis/builders/__init__.py +3 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/builders/elbuilders.py +88 -24
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/builders/manbuilder.py +88 -25
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/collectors.py +35 -25
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/eldef.py +18 -14
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/mandef.py +35 -22
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/manoption.py +4 -1
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/manparm.py +51 -38
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/operations/funopp.py +2 -1
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/operations/operation.py +1 -1
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/scheddef.py +2 -2
- flightanalysis-0.4.0/src/flightanalysis/elements/element.py +230 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/elements/line.py +12 -7
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/elements/loop.py +23 -11
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/elements/snap.py +24 -7
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/elements/spin.py +33 -12
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/elements/stall_turn.py +16 -3
- flightanalysis-0.4.0/src/flightanalysis/elements/tags.py +131 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/elements/tail_slide.py +7 -5
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/manoeuvre.py +23 -44
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/__init__.py +1 -1
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/box/box.py +14 -16
- flightanalysis-0.4.0/src/flightanalysis/scoring/box/parser.py +40 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/criteria/__init__.py +27 -4
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/criteria/criteria.py +10 -11
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/criteria/criteria_group.py +3 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/exponential.py +99 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/criteria/inter/combination.py +15 -7
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/criteria/inter/comparison.py +6 -2
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/criteria/intra/bounded.py +21 -5
- flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/intra/continuous.py +156 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/intra/deviation.py +36 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/intra/peak.py +72 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/intra/single.py +51 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/parser.py +49 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/downgrade/__init__.py +6 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/downgrade/base.py +38 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/downgrade/downgrade.py +131 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/downgrade/downgrade_pair.py +88 -0
- flightanalysis-0.4.0/src/flightanalysis/scoring/downgrade/downgrades.py +58 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/measurement.py +32 -21
- flightanalysis-0.4.0/src/flightanalysis/scoring/reffuncs.py +53 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/results/elements_results.py +26 -1
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/results/manoeuvre_results.py +19 -1
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/results/result.py +67 -56
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/results/results.py +71 -35
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/visibility.py +2 -12
- flightanalysis-0.4.0/tests/data/fcs_csv.csv +3 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/test_schedule_element_spin.py +5 -5
- {flightanalysis-0.3.20/tests → flightanalysis-0.4.0/tests/test_scoring}/test_criteria.py +8 -1
- flightanalysis-0.4.0/tests/test_scoring/test_lookup.py +11 -0
- flightanalysis-0.4.0/tests/test_utils.py +15 -0
- flightanalysis-0.3.20/src/flightanalysis/analysis/__init__.py +0 -3
- flightanalysis-0.3.20/src/flightanalysis/analysis/el_analysis.py +0 -145
- flightanalysis-0.3.20/src/flightanalysis/analysis/manoeuvre_analysis/__init__.py +0 -14
- flightanalysis-0.3.20/src/flightanalysis/analysis/manoeuvre_analysis/alignment.py +0 -131
- flightanalysis-0.3.20/src/flightanalysis/analysis/manoeuvre_analysis/analysis.py +0 -10
- flightanalysis-0.3.20/src/flightanalysis/analysis/manoeuvre_analysis/basic.py +0 -219
- flightanalysis-0.3.20/src/flightanalysis/analysis/manoeuvre_analysis/complete.py +0 -262
- flightanalysis-0.3.20/src/flightanalysis/analysis/manoeuvre_analysis/scored.py +0 -50
- flightanalysis-0.3.20/src/flightanalysis/base/ref_funcs.py +0 -80
- flightanalysis-0.3.20/src/flightanalysis/base/utils.py +0 -24
- flightanalysis-0.3.20/src/flightanalysis/builders/example/__init__.py +0 -4
- flightanalysis-0.3.20/src/flightanalysis/builders/example/box.py +0 -12
- flightanalysis-0.3.20/src/flightanalysis/builders/example/criteria.py +0 -30
- flightanalysis-0.3.20/src/flightanalysis/builders/example/downgrades.py +0 -22
- flightanalysis-0.3.20/src/flightanalysis/builders/example/manbuilder.py +0 -116
- flightanalysis-0.3.20/src/flightanalysis/definition/dg_applicator.py +0 -18
- flightanalysis-0.3.20/src/flightanalysis/elements/element.py +0 -121
- flightanalysis-0.3.20/src/flightanalysis/scoring/criteria/exponential.py +0 -57
- flightanalysis-0.3.20/src/flightanalysis/scoring/criteria/intra/continuous.py +0 -74
- flightanalysis-0.3.20/src/flightanalysis/scoring/criteria/intra/deviation.py +0 -15
- flightanalysis-0.3.20/src/flightanalysis/scoring/criteria/intra/peak.py +0 -38
- flightanalysis-0.3.20/src/flightanalysis/scoring/criteria/intra/single.py +0 -37
- flightanalysis-0.3.20/src/flightanalysis/scoring/downgrade.py +0 -154
- flightanalysis-0.3.20/src/flightanalysis/scoring/reffuncs.py +0 -39
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/.github/workflows/publish_pypi.yml +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/.gitignore +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/LICENSE +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/README.md +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/examples/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/examples/criteria.ipynb +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/examples/making_elements.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/examples/manoeuvre_definition.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/examples/reversing_spin.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/examples/tailslide.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/operations/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/operations/itemopp.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/operations/mathopp.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/definition/operations/sumopp.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/elements/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/py.typed +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/schedule.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/box/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/box/rectangular_box.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/box/triangular_box.py +0 -0
- {flightanalysis-0.3.20/src/flightanalysis/builders → flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/inter}/__init__.py +0 -0
- {flightanalysis-0.3.20/src/flightanalysis/scoring/criteria/inter → flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/intra}/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/results/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/results/dgplot.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/results/summary.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/scoring/results.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/src/flightanalysis/version.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/EmailedBox.f3a +0 -0
- {flightanalysis-0.3.20/src/flightanalysis/scoring/criteria/intra → flightanalysis-0.4.0/tests}/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/conftest.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/data/manual_F3A_P23.json +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/data/old_json.json +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/data/p23.BIN +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/data/p23_box.f3a +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/data/p23_fc.json +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/data/p23_flight.json +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/data/scored_fcj.json +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/data/unscored_fcj.json +0 -0
- {flightanalysis-0.3.20/tests → flightanalysis-0.4.0/tests/test_definition}/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_operations.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_rfuncs.py +0 -0
- {flightanalysis-0.3.20/tests/test_definition → flightanalysis-0.4.0/tests/test_schedule}/__init__.py +0 -0
- {flightanalysis-0.3.20/tests/test_schedule → flightanalysis-0.4.0/tests/test_schedule/test_element}/__init__.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/loop_analysis.json +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/p23_th_e0.csv +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/p23_th_e0.json +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/p23_th_e0_template.csv +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/test_schedule_element.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/test_schedule_element_line.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/test_schedule_element_loop.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/test_schedule_element_snap.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_element/test_schedule_element_stallturn.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/tests/test_schedule/test_manoeuvre.py +0 -0
- {flightanalysis-0.3.20/tests/test_schedule/test_element → flightanalysis-0.4.0/tests/test_scoring}/__init__.py +0 -0
- {flightanalysis-0.3.20/tests → flightanalysis-0.4.0/tests/test_scoring}/test_box.py +0 -0
- {flightanalysis-0.3.20 → flightanalysis-0.4.0}/uv.lock +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flightanalysis
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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.
|
|
9
|
+
Requires-Dist: flightdata>=0.3.5
|
|
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
|
+
Requires-Dist: more-itertools>=10.8.0
|
|
13
14
|
Requires-Dist: numpy>=2.1.3
|
|
14
15
|
Requires-Dist: pandas>=2.2.3
|
|
15
|
-
Requires-Dist: pfcschemas>=0.1.12
|
|
16
16
|
Requires-Dist: scipy>=1.14.1
|
|
17
17
|
Requires-Dist: simplejson>=3.19.3
|
|
18
18
|
Provides-Extra: dataflash
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flightanalysis"
|
|
3
|
-
version="0.
|
|
3
|
+
version="0.4.0"
|
|
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.
|
|
10
|
+
"flightdata>=0.3.5",
|
|
11
11
|
"joblib>=1.4.2",
|
|
12
12
|
"json-stream>=2.3.2",
|
|
13
13
|
"loguru>=0.7.2",
|
|
14
|
+
"more-itertools>=10.8.0",
|
|
14
15
|
"numpy>=2.1.3",
|
|
15
16
|
"pandas>=2.2.3",
|
|
16
|
-
"pfcschemas>=0.1.12",
|
|
17
17
|
"scipy>=1.14.1",
|
|
18
18
|
"simplejson>=3.19.3",
|
|
19
19
|
]
|
|
@@ -4,7 +4,7 @@ from .manoeuvre import Manoeuvre # noqa: F401
|
|
|
4
4
|
from .schedule import Schedule # noqa: F401
|
|
5
5
|
from .definition import * # noqa: F403
|
|
6
6
|
from .scoring import * # noqa: F403
|
|
7
|
-
from .analysis import ScheduleAnalysis, ElementAnalysis,
|
|
7
|
+
from .analysis import ScheduleAnalysis, ElementAnalysis, Analysis # noqa: F401
|
|
8
8
|
|
|
9
9
|
import sys
|
|
10
10
|
from loguru import logger
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from flightanalysis.scoring.results import Results
|
|
3
|
+
from flightanalysis.definition import ElDef, ManDef
|
|
4
|
+
from flightanalysis.elements import AnyElement, Elements
|
|
5
|
+
from flightanalysis.manoeuvre import Manoeuvre
|
|
6
|
+
from flightdata import State
|
|
7
|
+
import geometry as g
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_score(
|
|
12
|
+
ed: ElDef, el: AnyElement, itrans: g.Transformation, fl: State
|
|
13
|
+
) -> tuple[Results, g.Transformation]:
|
|
14
|
+
el: AnyElement = el.match_intention(itrans, fl)
|
|
15
|
+
tp = el.create_template(State.from_transform(itrans), fl)
|
|
16
|
+
return ed.dgs.apply(el, fl, tp), el, tp # tp[-1].att
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _optimise_split(
|
|
20
|
+
mdef: ManDef,
|
|
21
|
+
manoeuvre: Manoeuvre,
|
|
22
|
+
templates: dict[str, State],
|
|
23
|
+
eln1: str,
|
|
24
|
+
eln2: str,
|
|
25
|
+
itrans: g.Transformation,
|
|
26
|
+
fl: State,
|
|
27
|
+
min_len: int = 3,
|
|
28
|
+
step_size: int = 1,
|
|
29
|
+
include_inter: bool = True,
|
|
30
|
+
) -> int:
|
|
31
|
+
def score_split(steps: int) -> float:
|
|
32
|
+
new_fl = fl.step_label("element", eln1, steps, fl.t, min_len)
|
|
33
|
+
res1, oel1, tp1 = get_score(
|
|
34
|
+
mdef.eds[eln1], manoeuvre.elements[eln1], itrans, getattr(new_fl.element, eln1)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
el2fl = getattr(new_fl.element, eln2)
|
|
38
|
+
res2, oel2, tp2 = get_score(
|
|
39
|
+
mdef.eds[eln2], manoeuvre.elements[eln2], g.Transformation(tp1[-1].att, el2fl[0].pos), el2fl
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if include_inter:
|
|
43
|
+
oman = Manoeuvre.from_all_elements(
|
|
44
|
+
manoeuvre.uid,
|
|
45
|
+
Elements(manoeuvre.elements.data | {oel1.uid: oel1, oel2.uid: oel2}),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
omdef = mdef.update_defaults(oman)
|
|
49
|
+
|
|
50
|
+
inter = omdef.mps.collect(
|
|
51
|
+
oman,
|
|
52
|
+
State.stack(templates | {oel1.uid: tp1, oel2.uid: tp2}, "element"),
|
|
53
|
+
mdef.box,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
logger.debug(f"split {steps} {res1.total + res2.total:.2f}")
|
|
57
|
+
logger.debug(
|
|
58
|
+
f"e1={oel1.uid}, e2={oel2.uid}, steps={steps}, intra={res1.total + res2.total:.2f}, inter={inter.total if include_inter else 'not included'}"
|
|
59
|
+
)
|
|
60
|
+
return res1.total + res2.total + (inter.total if include_inter else 0)
|
|
61
|
+
|
|
62
|
+
dgs = {0: score_split(0)}
|
|
63
|
+
|
|
64
|
+
def check_steps(stps: int):
|
|
65
|
+
new_l2 = len(getattr(fl.element, eln2)) - stps + 1
|
|
66
|
+
new_l1 = len(getattr(fl.element, eln1)) + stps + 1
|
|
67
|
+
return new_l2 > min_len and new_l1 > min_len
|
|
68
|
+
|
|
69
|
+
steps = step_size * (int(len(getattr(fl.element, eln1)) > len(getattr(fl.element, eln2))) * 2 - 1)
|
|
70
|
+
|
|
71
|
+
if not check_steps(steps):
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
new_dg = score_split(steps)
|
|
76
|
+
if new_dg > dgs[0]:
|
|
77
|
+
steps = -steps
|
|
78
|
+
else:
|
|
79
|
+
dgs[steps] = new_dg
|
|
80
|
+
steps += np.sign(steps) * step_size
|
|
81
|
+
except Exception:
|
|
82
|
+
steps = -steps
|
|
83
|
+
|
|
84
|
+
while check_steps(steps):
|
|
85
|
+
try:
|
|
86
|
+
new_dg = score_split(steps)
|
|
87
|
+
if new_dg < list(dgs.values())[-1]:
|
|
88
|
+
dgs[steps] = new_dg
|
|
89
|
+
steps += np.sign(steps) * step_size
|
|
90
|
+
else:
|
|
91
|
+
break
|
|
92
|
+
except ValueError:
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
min_dg_step = np.argmin(np.array(list(dgs.values())))
|
|
96
|
+
out_steps = list(dgs.keys())[min_dg_step]
|
|
97
|
+
return out_steps
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def optimise_alignment_old(
|
|
101
|
+
flown: State, mdef: ManDef, manoeuvre: Manoeuvre, templates: dict[str, State]
|
|
102
|
+
) -> State:
|
|
103
|
+
elns = list(mdef.eds.data.keys())
|
|
104
|
+
|
|
105
|
+
padjusted = set(elns)
|
|
106
|
+
count = 0
|
|
107
|
+
while len(padjusted) > 0 and count < 2:
|
|
108
|
+
adjusted = set()
|
|
109
|
+
for eln1, eln2 in zip(elns[:-1], elns[1:]):
|
|
110
|
+
if (eln1 in padjusted) or (eln2 in padjusted):
|
|
111
|
+
itrans = g.Transformation(
|
|
112
|
+
templates[eln1][0].att,
|
|
113
|
+
flown.element[eln1][0].pos,
|
|
114
|
+
)
|
|
115
|
+
steps = _optimise_split(
|
|
116
|
+
mdef,
|
|
117
|
+
manoeuvre,
|
|
118
|
+
templates,
|
|
119
|
+
eln1, # flown.element[eln1][0],
|
|
120
|
+
eln2, # flown.element[eln2][0],
|
|
121
|
+
itrans,
|
|
122
|
+
flown,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if not steps == 0:
|
|
126
|
+
logger.debug(
|
|
127
|
+
f"Adjusting split between {eln1} and {eln2} by {steps} steps"
|
|
128
|
+
)
|
|
129
|
+
flown = flown.step_label("element", eln1, steps, flown.t, 3)
|
|
130
|
+
adjusted.update([eln1, eln2])
|
|
131
|
+
|
|
132
|
+
padjusted = adjusted
|
|
133
|
+
count += 1
|
|
134
|
+
logger.debug(f"pass {count}, {len(padjusted)} elements adjusted:\n{padjusted}")
|
|
135
|
+
|
|
136
|
+
return flown
|
|
137
|
+
|
|
138
|
+
def optimise_alignment(
|
|
139
|
+
flown: State, mdef: ManDef, manoeuvre: Manoeuvre, templates: dict[str, State]
|
|
140
|
+
) -> State:
|
|
141
|
+
|
|
142
|
+
elns = list(mdef.eds.data.keys())
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
for eln1, eln2 in zip(elns[:-1], elns[1:]):
|
|
147
|
+
for step_size in [5, 1]:
|
|
148
|
+
itrans = g.Transformation(
|
|
149
|
+
templates[eln1][0].att,
|
|
150
|
+
flown.element[eln1][0].pos,
|
|
151
|
+
)
|
|
152
|
+
steps = _optimise_split(
|
|
153
|
+
mdef,
|
|
154
|
+
manoeuvre,
|
|
155
|
+
templates,
|
|
156
|
+
eln1, # flown.element[eln1][0],
|
|
157
|
+
eln2, # flown.element[eln2][0],
|
|
158
|
+
itrans,
|
|
159
|
+
flown,
|
|
160
|
+
3,
|
|
161
|
+
step_size,
|
|
162
|
+
True
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if not steps == 0:
|
|
166
|
+
logger.debug(
|
|
167
|
+
f"Adjusting split between {eln1} and {eln2} by {steps} steps"
|
|
168
|
+
)
|
|
169
|
+
flown = flown.step_label("element", eln1, steps, flown.t, 3)
|
|
170
|
+
|
|
171
|
+
return flown
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from flightanalysis.scoring.results import Results, Result
|
|
5
|
+
from flightdata import State
|
|
6
|
+
from typing import Self
|
|
7
|
+
from flightanalysis import ElDef, Element, ManParms
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
import geometry as g
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ElementAnalysis:
|
|
14
|
+
edef: ElDef
|
|
15
|
+
mps: ManParms
|
|
16
|
+
el: Element
|
|
17
|
+
fl: State
|
|
18
|
+
tp: State
|
|
19
|
+
ref_frame: g.Transformation
|
|
20
|
+
results: Results | None = None
|
|
21
|
+
|
|
22
|
+
def update(self, new_fl: State):
|
|
23
|
+
new_el = self.el.match_intention(self.ref_frame, new_fl)
|
|
24
|
+
new_tp = new_el.create_template(self.tp[0], new_fl.time)
|
|
25
|
+
return ElementAnalysis(
|
|
26
|
+
self.edef, self.mps, new_el, new_fl, new_tp, self.ref_frame
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def plot_3d(self, **kwargs):
|
|
30
|
+
from plotting import plotsec
|
|
31
|
+
|
|
32
|
+
return plotsec(dict(fl=self.fl, tp=self.tp), **kwargs)
|
|
33
|
+
|
|
34
|
+
def to_dict(self):
|
|
35
|
+
return {k: v.to_dict() for k, v in self.__dict__.items()}
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def from_dict(data) -> Self:
|
|
39
|
+
mps = ManParms.from_dict(data["mps"])
|
|
40
|
+
return ElementAnalysis(
|
|
41
|
+
ElDef.from_dict(data["edef"], mps),
|
|
42
|
+
mps,
|
|
43
|
+
Element.from_dict(data["el"]),
|
|
44
|
+
State.from_dict(data["fl"]),
|
|
45
|
+
State.from_dict(data["tp"]),
|
|
46
|
+
g.Transformation.from_dict(data["ref_frame"]),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def score_dg(self, dg: str, limits: bool = True) -> Result:
|
|
50
|
+
return self.edef.dgs[dg](self.el, self.fl, self.tp, limits)
|
|
51
|
+
|
|
52
|
+
def intra_score(self) -> Results:
|
|
53
|
+
try:
|
|
54
|
+
return self.edef.dgs.apply(
|
|
55
|
+
self.el, self.fl, self.tp
|
|
56
|
+
)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
raise Exception(f"{self.el.uid}: {e}") from e
|
|
59
|
+
|
|
60
|
+
def info(self):
|
|
61
|
+
return dict(
|
|
62
|
+
element=self.el.uid,
|
|
63
|
+
**(dict(angle=np.degrees(self.el.angle)) if "angle" in self.el.parameters else {}),
|
|
64
|
+
**(dict(radius=self.el.radius) if "radius" in self.el.parameters else {}),
|
|
65
|
+
**(dict(rolls=np.degrees(self.el.rolls)) if "rolls" in self.el.parameters else {}),
|
|
66
|
+
**(dict(roll=np.degrees(self.el.roll)) if "roll" in self.el.parameters else {}),
|
|
67
|
+
freq=f"{1 / self.fl.dt.mean():.0f}",
|
|
68
|
+
len=len(self.fl),
|
|
69
|
+
**(dict(intra=f"{self.results.total:.4f}") if self.results is not None else {}),
|
|
70
|
+
) # fmt: skip
|
|
71
|
+
|
|
72
|
+
def full_result_plot(self, name: str, showlegend=True, textposition: str = "bottom left"):
|
|
73
|
+
"""plot the actual result and the uncropped result on the same figure,
|
|
74
|
+
include some info from the ddowngrade"""
|
|
75
|
+
import plotly.graph_objects as go
|
|
76
|
+
|
|
77
|
+
dg = self.edef.dgs[name]
|
|
78
|
+
full_res: Result = dg(self.el, self.fl, self.tp, True, select=False)
|
|
79
|
+
crop_res: Result = dg(self.el, self.fl, self.tp, True, select=True)
|
|
80
|
+
full_xvals = self.fl.t - self.fl.t[0]
|
|
81
|
+
crop_xvals = np.array(g.utils.get_value(full_xvals, crop_res.sample_keys))
|
|
82
|
+
|
|
83
|
+
u = full_res.plot_f
|
|
84
|
+
|
|
85
|
+
fig = go.Figure(
|
|
86
|
+
layout=dict(
|
|
87
|
+
yaxis2=dict(
|
|
88
|
+
title="Visibility", overlaying="y", range=[0, 1], side="right"
|
|
89
|
+
),
|
|
90
|
+
title=f"{crop_res.name}, {crop_res.total:.2f}",
|
|
91
|
+
legend=dict(orientation="h", x=0, y=1.4, yanchor="top"),
|
|
92
|
+
margin=dict(t=10, r=90, b=90, l=90),
|
|
93
|
+
xaxis=dict(
|
|
94
|
+
visible=True,
|
|
95
|
+
title="Time (s)",
|
|
96
|
+
range=[
|
|
97
|
+
0,
|
|
98
|
+
full_xvals[-1]
|
|
99
|
+
if full_xvals is not None
|
|
100
|
+
else len(self.measurement),
|
|
101
|
+
],
|
|
102
|
+
),
|
|
103
|
+
yaxis=dict(
|
|
104
|
+
title=f"Measurement ({crop_res.measurement.unit.replace('rad', 'degrees')})"
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
if len(crop_res.measurement) < len(full_res.measurement):
|
|
109
|
+
fig.add_trace(
|
|
110
|
+
go.Scatter(
|
|
111
|
+
x=full_xvals,
|
|
112
|
+
y=u(full_res.measurement.value),
|
|
113
|
+
name="Measurement",
|
|
114
|
+
mode="lines",
|
|
115
|
+
line=dict(color="blue", width=1, dash="dash"),
|
|
116
|
+
showlegend=showlegend
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
fig.add_trace(
|
|
121
|
+
go.Scatter(
|
|
122
|
+
x=crop_xvals,
|
|
123
|
+
y=u(crop_res.measurement.value),
|
|
124
|
+
name="Selected" if len(crop_res.measurement) < len(full_res.measurement) else "Measurement",
|
|
125
|
+
mode="lines",
|
|
126
|
+
line=dict(color="blue", width=1, dash="solid"),
|
|
127
|
+
showlegend=showlegend
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if len(dg.smoothers) > 1:
|
|
132
|
+
fig.add_trace(
|
|
133
|
+
go.Scatter(
|
|
134
|
+
x=crop_xvals,
|
|
135
|
+
y=u(crop_res.raw_sample),
|
|
136
|
+
name="Raw Sample",
|
|
137
|
+
mode="lines",
|
|
138
|
+
line=dict(color="black", width=1, dash="dash"),
|
|
139
|
+
showlegend=showlegend
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
fig.add_trace(
|
|
143
|
+
go.Scatter(
|
|
144
|
+
x=crop_xvals,
|
|
145
|
+
y=u(crop_res.sample),
|
|
146
|
+
name="Smooth Sample" if len(dg.smoothers) > 1 else "Sample",
|
|
147
|
+
mode="lines",
|
|
148
|
+
line=dict(color="black", width=1, dash="solid"),
|
|
149
|
+
showlegend=showlegend
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
fig.add_trace(
|
|
153
|
+
go.Scatter(
|
|
154
|
+
x=crop_res.sample_keys[self.keys]
|
|
155
|
+
if crop_xvals is None
|
|
156
|
+
else crop_xvals[crop_res.keys],
|
|
157
|
+
y=u(crop_res.sample[crop_res.keys]),
|
|
158
|
+
text=np.round(crop_res.dgs, 3),
|
|
159
|
+
hovertext=[crop_res.info(i) for i in range(len(crop_res.keys))],
|
|
160
|
+
mode="markers",
|
|
161
|
+
name="Downgrades",
|
|
162
|
+
textposition=textposition,
|
|
163
|
+
yaxis="y",
|
|
164
|
+
marker=dict(size=10, color="black"),
|
|
165
|
+
showlegend=showlegend
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
fig.add_trace(
|
|
169
|
+
go.Scatter(
|
|
170
|
+
x=full_xvals,
|
|
171
|
+
y=full_res.measurement.visibility,
|
|
172
|
+
mode="lines",
|
|
173
|
+
name="Visibility",
|
|
174
|
+
yaxis="y2",
|
|
175
|
+
line=dict(width=1, color="black", dash="dot"),
|
|
176
|
+
showlegend=showlegend
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
fig.add_vrect(
|
|
181
|
+
x0=full_xvals[0],
|
|
182
|
+
x1=crop_xvals[0],
|
|
183
|
+
fillcolor="grey",
|
|
184
|
+
opacity=0.2,
|
|
185
|
+
line_width=0,
|
|
186
|
+
showlegend=False
|
|
187
|
+
)
|
|
188
|
+
fig.add_vrect(
|
|
189
|
+
x0=crop_xvals[-1],
|
|
190
|
+
x1=full_xvals[-1],
|
|
191
|
+
fillcolor="grey",
|
|
192
|
+
opacity=0.2,
|
|
193
|
+
line_width=0,
|
|
194
|
+
showlegend=False
|
|
195
|
+
)
|
|
196
|
+
if crop_res.criteria.__class__.__name__ == "Bounded":
|
|
197
|
+
fig.add_shape(
|
|
198
|
+
type="rect",
|
|
199
|
+
x0=crop_xvals[0],
|
|
200
|
+
x1=crop_xvals[-1],
|
|
201
|
+
y0=u(crop_res.criteria.max_bound) if crop_res.criteria.max_bound is not None else -50,
|
|
202
|
+
y1=u(crop_res.criteria.min_bound) if crop_res.criteria.min_bound is not None else 50,
|
|
203
|
+
fillcolor="red",
|
|
204
|
+
opacity=0.2,
|
|
205
|
+
line_width=0,
|
|
206
|
+
showlegend=False
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return fig
|
|
210
|
+
|
|
211
|
+
def results_subset_plot(self, names: list[str]):
|
|
212
|
+
from plotly.subplots import make_subplots
|
|
213
|
+
|
|
214
|
+
figs = [self.full_result_plot(name, i==0, "bottom left") for i, name in enumerate(names)]
|
|
215
|
+
|
|
216
|
+
fig = make_subplots(
|
|
217
|
+
rows=len(figs),
|
|
218
|
+
cols=1,
|
|
219
|
+
shared_xaxes=True,
|
|
220
|
+
specs=[[{"secondary_y": True}] for _ in range(len(figs))],
|
|
221
|
+
vertical_spacing=0.06,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
for i, _fig in enumerate(figs, 1):
|
|
225
|
+
for tr in _fig.data:
|
|
226
|
+
fig.add_trace(tr, row=i, col=1, secondary_y=tr.name == "Visibility")
|
|
227
|
+
for sh in _fig.layout.shapes:
|
|
228
|
+
fig.add_shape(sh, row=i, col=1)
|
|
229
|
+
|
|
230
|
+
axprops = dict(zerolinecolor="RGBA(0, 0, 0, 0.2)", zerolinewidth=2)
|
|
231
|
+
|
|
232
|
+
axes = {}
|
|
233
|
+
for i, _fig in enumerate(figs, 1):
|
|
234
|
+
axes[f"xaxis{i if i > 1 else ''}"] = dict(
|
|
235
|
+
visible=True, range=[0, self.fl.duration], **axprops
|
|
236
|
+
) | (dict(title="Time (s)") if i == len(figs) else {})
|
|
237
|
+
axes[f"yaxis{2*i-1 if i>1 else ''}"] = dict(title=names[i-1], **axprops)
|
|
238
|
+
axes[f"yaxis{2*i}"] = dict(title="Visibility", overlaying=f"y{2*i-1 if i>1 else ''}", range=[0, 1], side="right", **axprops)
|
|
239
|
+
|
|
240
|
+
fig.update_layout(
|
|
241
|
+
legend=dict(orientation="h", x=0, y=1 + 0.4 / len(figs), yanchor="top"),
|
|
242
|
+
margin=dict(t=10, r=70, b=70, l=70),
|
|
243
|
+
width=1000,
|
|
244
|
+
height=250 * len(figs) + 50,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
fig.update_layout(**axes)
|
|
249
|
+
return fig
|
|
250
|
+
|
|
251
|
+
def plot_results(self, name: str):
|
|
252
|
+
import plotly.graph_objects as go
|
|
253
|
+
from plotly.subplots import make_subplots
|
|
254
|
+
|
|
255
|
+
result: Result = self.results[name]
|
|
256
|
+
|
|
257
|
+
fig = make_subplots(
|
|
258
|
+
1, 2,
|
|
259
|
+
column_widths=[0.25, 0.75],
|
|
260
|
+
specs=[[{"secondary_y": False,},{"secondary_y": True}]],
|
|
261
|
+
) # fmt: skip
|
|
262
|
+
|
|
263
|
+
fig.add_trace(self.fl.pos.plotxz().data[0], row=1, col=1)
|
|
264
|
+
|
|
265
|
+
fig.add_traces(
|
|
266
|
+
[
|
|
267
|
+
*result.measurement_trace(),
|
|
268
|
+
*result.sample_trace(),
|
|
269
|
+
result.downgrade_trace(),
|
|
270
|
+
],
|
|
271
|
+
rows=1,
|
|
272
|
+
cols=2,
|
|
273
|
+
)
|
|
274
|
+
fig.add_trace(
|
|
275
|
+
result.visibility_trace(),
|
|
276
|
+
row=1,
|
|
277
|
+
col=2,
|
|
278
|
+
secondary_y=True,
|
|
279
|
+
)
|
|
280
|
+
info = dict(
|
|
281
|
+
**self.info(),
|
|
282
|
+
**{"downgrade": result.total},
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def format_float(v):
|
|
286
|
+
return f"{v:.2f}" if isinstance(v, float) else v
|
|
287
|
+
|
|
288
|
+
text = "<br>".join([f"{k}={format_float(v)}" for k, v in info.items()])
|
|
289
|
+
|
|
290
|
+
fig.update_layout(
|
|
291
|
+
yaxis=dict(scaleanchor="x", scaleratio=1),
|
|
292
|
+
xaxis2=dict(range=[0, len(self.fl)]),
|
|
293
|
+
height=300,
|
|
294
|
+
legend=dict(
|
|
295
|
+
orientation="h",
|
|
296
|
+
yanchor="top",
|
|
297
|
+
y=1.0,
|
|
298
|
+
xanchor="left",
|
|
299
|
+
x=0.3,
|
|
300
|
+
bgcolor="rgba(0,0,0,0)",
|
|
301
|
+
),
|
|
302
|
+
margin=dict(l=0, r=0, t=0, b=0),
|
|
303
|
+
# yaxis2=dict(range=[0, 2]),
|
|
304
|
+
yaxis3=dict(range=[0, 1]),
|
|
305
|
+
annotations=[
|
|
306
|
+
go.layout.Annotation(
|
|
307
|
+
text=text,
|
|
308
|
+
align="left",
|
|
309
|
+
showarrow=False,
|
|
310
|
+
xref="paper",
|
|
311
|
+
yref="paper",
|
|
312
|
+
x=0.2,
|
|
313
|
+
y=0.8,
|
|
314
|
+
bordercolor="black",
|
|
315
|
+
borderwidth=1,
|
|
316
|
+
)
|
|
317
|
+
],
|
|
318
|
+
scene=dict(
|
|
319
|
+
aspectmode="data",
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return fig
|
|
324
|
+
|
|
325
|
+
def axisplot(self, camera: dict = None, **kwargs):
|
|
326
|
+
import plotly.graph_objects as go
|
|
327
|
+
|
|
328
|
+
fig: go.Figure = self.plot_3d(**kwargs)
|
|
329
|
+
fig.update_layout(
|
|
330
|
+
scene=dict(
|
|
331
|
+
camera=dict(
|
|
332
|
+
eye=dict(x=0, y=-1, z=0),
|
|
333
|
+
projection=dict(type="orthographic"),
|
|
334
|
+
)
|
|
335
|
+
| (camera if camera is not None else {}),
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
return fig
|