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.
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/PKG-INFO +2 -2
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/pyproject.toml +2 -2
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/__init__.py +0 -15
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/el_analysis.py +13 -3
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/manoeuvre_analysis.py +171 -56
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/schedule_analysis.py +24 -23
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/base/ref_funcs.py +1 -2
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/base/utils.py +28 -13
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/builders/elbuilders.py +2 -1
- flightanalysis-0.4.1/src/flightanalysis/builders/element_descriptions.py +111 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/builders/manbuilder.py +5 -4
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/__init__.py +6 -2
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/collectors.py +2 -3
- flightanalysis-0.4.1/src/flightanalysis/definition/combo_setting.py +32 -0
- flightanalysis-0.4.1/src/flightanalysis/definition/combo_settings.py +118 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/eldef.py +3 -4
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/mandef.py +27 -14
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/manparm.py +4 -158
- flightanalysis-0.4.1/src/flightanalysis/definition/manparms.py +249 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/operation.py +5 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/scheddef.py +4 -5
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/__init__.py +2 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/element.py +26 -3
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/spin.py +9 -5
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/tags.py +19 -1
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/manoeuvre.py +25 -45
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/schedule.py +1 -2
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/box.py +13 -12
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/parser.py +1 -1
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/__init__.py +3 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/criteria.py +9 -4
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/criteria_group.py +1 -2
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/exponential.py +10 -7
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/bounded.py +25 -18
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/continuous.py +3 -3
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/deviation.py +5 -4
- flightanalysis-0.4.1/src/flightanalysis/scoring/criteria/intra/peak.py +82 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/single.py +3 -3
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/parser.py +2 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/downgrade/base.py +5 -1
- flightanalysis-0.4.1/src/flightanalysis/scoring/downgrade/dg_testing.py +39 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/downgrade/downgrade.py +57 -15
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/downgrade/downgrade_pair.py +2 -1
- flightanalysis-0.4.1/src/flightanalysis/scoring/downgrade/downgrades.py +74 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/dgplot.py +0 -7
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/elements_results.py +1 -3
- flightanalysis-0.4.1/src/flightanalysis/scoring/results/result.py +255 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/results.py +31 -34
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results.py +2 -6
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/visibility.py +10 -3
- flightanalysis-0.4.1/tests/data/fcs_csv.csv +4 -0
- flightanalysis-0.4.1/tests/test_manparms.py +102 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_operations.py +2 -1
- flightanalysis-0.4.1/tests/test_scoring/test_criteria_bounded.py +82 -0
- flightanalysis-0.4.1/tests/test_scoring/test_criteria_continuous.py +82 -0
- flightanalysis-0.4.1/tests/test_scoring/test_criteria_peak.py +37 -0
- flightanalysis-0.4.1/tests/test_scoring/test_criteria_single.py +28 -0
- flightanalysis-0.4.1/tests/test_scoring/test_inter_criteria.py +54 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_utils.py +9 -1
- flightanalysis-0.4.0/src/flightanalysis/analysis/aligment_optimisation.py +0 -171
- flightanalysis-0.4.0/src/flightanalysis/scoring/criteria/intra/peak.py +0 -72
- flightanalysis-0.4.0/src/flightanalysis/scoring/downgrade/downgrades.py +0 -58
- flightanalysis-0.4.0/src/flightanalysis/scoring/results/result.py +0 -312
- flightanalysis-0.4.0/tests/data/fcs_csv.csv +0 -3
- flightanalysis-0.4.0/tests/test_scoring/__init__.py +0 -0
- flightanalysis-0.4.0/tests/test_scoring/test_criteria.py +0 -218
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/.github/workflows/publish_pypi.yml +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/.gitignore +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/LICENSE +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/README.md +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/criteria.ipynb +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/making_elements.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/manoeuvre_definition.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/reversing_spin.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/examples/tailslide.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/builders/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/manoption.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/funopp.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/itemopp.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/mathopp.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/definition/operations/sumopp.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/line.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/loop.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/snap.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/stall_turn.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/elements/tail_slide.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/py.typed +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/rectangular_box.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/box/triangular_box.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/inter/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/inter/combination.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/inter/comparison.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/criteria/intra/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/downgrade/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/measurement.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/reffuncs.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/manoeuvre_results.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/scoring/results/summary.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/version.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/EmailedBox.f3a +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/conftest.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/manual_F3A_P23.json +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/old_json.json +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/p23.BIN +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/p23_box.f3a +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/p23_fc.json +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/p23_flight.json +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/scored_fcj.json +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/data/unscored_fcj.json +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_rfuncs.py +0 -0
- {flightanalysis-0.4.0/tests/test_definition → flightanalysis-0.4.1/tests/test_schedule}/__init__.py +0 -0
- {flightanalysis-0.4.0/tests/test_schedule → flightanalysis-0.4.1/tests/test_schedule/test_element}/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/loop_analysis.json +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/p23_th_e0.csv +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/p23_th_e0.json +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/p23_th_e0_template.csv +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_line.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_loop.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_snap.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_spin.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_element/test_schedule_element_stallturn.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_schedule/test_manoeuvre.py +0 -0
- {flightanalysis-0.4.0/tests/test_schedule/test_element → flightanalysis-0.4.1/tests/test_scoring}/__init__.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_scoring/test_box.py +0 -0
- {flightanalysis-0.4.0 → flightanalysis-0.4.1}/tests/test_scoring/test_lookup.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
|
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"""
|
{flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/manoeuvre_analysis.py
RENAMED
|
@@ -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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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)
|
|
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()
|
|
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()
|
|
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()
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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(
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
manoeuvre = None
|
|
389
|
+
manoeuvre = (
|
|
390
|
+
Manoeuvre.from_dict(data["manoeuvre"]) if data.get("manoeuvre") else None
|
|
391
|
+
)
|
|
279
392
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
{flightanalysis-0.4.0 → flightanalysis-0.4.1}/src/flightanalysis/analysis/schedule_analysis.py
RENAMED
|
@@ -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(
|
|
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,
|
|
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)(
|
|
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)
|
|
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(
|
|
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}
|
|
@@ -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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
60
|
-
if
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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
|