ewgeo 0.0.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.
- ewgeo-0.0.1/LICENSE +21 -0
- ewgeo-0.0.1/PKG-INFO +90 -0
- ewgeo-0.0.1/README.md +75 -0
- ewgeo-0.0.1/pyproject.toml +27 -0
- ewgeo-0.0.1/setup.cfg +4 -0
- ewgeo-0.0.1/src/ewgeo/aoa/__init__.py +5 -0
- ewgeo-0.0.1/src/ewgeo/aoa/aoa.py +56 -0
- ewgeo-0.0.1/src/ewgeo/aoa/directional.py +255 -0
- ewgeo-0.0.1/src/ewgeo/aoa/doppler.py +233 -0
- ewgeo-0.0.1/src/ewgeo/aoa/interferometer.py +149 -0
- ewgeo-0.0.1/src/ewgeo/aoa/watson_watt.py +154 -0
- ewgeo-0.0.1/src/ewgeo/array_df/__init__.py +3 -0
- ewgeo-0.0.1/src/ewgeo/array_df/model.py +97 -0
- ewgeo-0.0.1/src/ewgeo/array_df/perf.py +91 -0
- ewgeo-0.0.1/src/ewgeo/array_df/solvers.py +157 -0
- ewgeo-0.0.1/src/ewgeo/atm/__init__.py +3 -0
- ewgeo-0.0.1/src/ewgeo/atm/model.py +348 -0
- ewgeo-0.0.1/src/ewgeo/atm/reference.py +239 -0
- ewgeo-0.0.1/src/ewgeo/atm/test.py +140 -0
- ewgeo-0.0.1/src/ewgeo/detector/__init__.py +2 -0
- ewgeo-0.0.1/src/ewgeo/detector/squareLaw.py +204 -0
- ewgeo-0.0.1/src/ewgeo/detector/xcorr.py +176 -0
- ewgeo-0.0.1/src/ewgeo/fdoa/__init__.py +3 -0
- ewgeo-0.0.1/src/ewgeo/fdoa/model.py +637 -0
- ewgeo-0.0.1/src/ewgeo/fdoa/perf.py +118 -0
- ewgeo-0.0.1/src/ewgeo/fdoa/system.py +157 -0
- ewgeo-0.0.1/src/ewgeo/hybrid/__init__.py +3 -0
- ewgeo-0.0.1/src/ewgeo/hybrid/model.py +535 -0
- ewgeo-0.0.1/src/ewgeo/hybrid/perf.py +66 -0
- ewgeo-0.0.1/src/ewgeo/hybrid/system.py +785 -0
- ewgeo-0.0.1/src/ewgeo/noise/__init__.py +1 -0
- ewgeo-0.0.1/src/ewgeo/noise/model.py +189 -0
- ewgeo-0.0.1/src/ewgeo/prop/__init__.py +1 -0
- ewgeo-0.0.1/src/ewgeo/prop/model.py +228 -0
- ewgeo-0.0.1/src/ewgeo/tdoa/__init__.py +4 -0
- ewgeo-0.0.1/src/ewgeo/tdoa/model.py +566 -0
- ewgeo-0.0.1/src/ewgeo/tdoa/perf.py +55 -0
- ewgeo-0.0.1/src/ewgeo/tdoa/solvers.py +187 -0
- ewgeo-0.0.1/src/ewgeo/tdoa/system.py +151 -0
- ewgeo-0.0.1/src/ewgeo/tracker/__init__.py +15 -0
- ewgeo-0.0.1/src/ewgeo/tracker/association.py +613 -0
- ewgeo-0.0.1/src/ewgeo/tracker/deleter.py +18 -0
- ewgeo-0.0.1/src/ewgeo/tracker/initiator.py +33 -0
- ewgeo-0.0.1/src/ewgeo/tracker/measurement.py +203 -0
- ewgeo-0.0.1/src/ewgeo/tracker/promoter.py +32 -0
- ewgeo-0.0.1/src/ewgeo/tracker/states.py +216 -0
- ewgeo-0.0.1/src/ewgeo/tracker/track.py +171 -0
- ewgeo-0.0.1/src/ewgeo/tracker/tracker.py +244 -0
- ewgeo-0.0.1/src/ewgeo/tracker/transition.py +355 -0
- ewgeo-0.0.1/src/ewgeo/triang/__init__.py +4 -0
- ewgeo-0.0.1/src/ewgeo/triang/model.py +471 -0
- ewgeo-0.0.1/src/ewgeo/triang/perf.py +48 -0
- ewgeo-0.0.1/src/ewgeo/triang/solvers.py +133 -0
- ewgeo-0.0.1/src/ewgeo/triang/system.py +209 -0
- ewgeo-0.0.1/src/ewgeo/utils/__init__.py +12 -0
- ewgeo-0.0.1/src/ewgeo/utils/constants.py +24 -0
- ewgeo-0.0.1/src/ewgeo/utils/constraints.py +568 -0
- ewgeo-0.0.1/src/ewgeo/utils/coordinates.py +647 -0
- ewgeo-0.0.1/src/ewgeo/utils/covariance.py +591 -0
- ewgeo-0.0.1/src/ewgeo/utils/errors.py +302 -0
- ewgeo-0.0.1/src/ewgeo/utils/geo.py +325 -0
- ewgeo-0.0.1/src/ewgeo/utils/perf.py +118 -0
- ewgeo-0.0.1/src/ewgeo/utils/search_space.py +284 -0
- ewgeo-0.0.1/src/ewgeo/utils/solvers.py +436 -0
- ewgeo-0.0.1/src/ewgeo/utils/system.py +1343 -0
- ewgeo-0.0.1/src/ewgeo/utils/unit_conversions.py +158 -0
- ewgeo-0.0.1/src/ewgeo/utils/utils.py +721 -0
- ewgeo-0.0.1/src/ewgeo.egg-info/PKG-INFO +90 -0
- ewgeo-0.0.1/src/ewgeo.egg-info/SOURCES.txt +78 -0
- ewgeo-0.0.1/src/ewgeo.egg-info/dependency_links.txt +1 -0
- ewgeo-0.0.1/src/ewgeo.egg-info/requires.txt +4 -0
- ewgeo-0.0.1/src/ewgeo.egg-info/top_level.txt +1 -0
- ewgeo-0.0.1/tests/test_aoa_gain_functions.py +44 -0
- ewgeo-0.0.1/tests/test_constraints.py +100 -0
- ewgeo-0.0.1/tests/test_coordinates.py +281 -0
- ewgeo-0.0.1/tests/test_covariance.py +332 -0
- ewgeo-0.0.1/tests/test_crlb.py +95 -0
- ewgeo-0.0.1/tests/test_errors.py +54 -0
- ewgeo-0.0.1/tests/test_geo.py +114 -0
- ewgeo-0.0.1/tests/test_measurement.py +99 -0
ewgeo-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Nicholas A O'Donoughue
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ewgeo-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ewgeo
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python code companion to Emitter Detection and Geolocation for Electronic Warfare (Artech House, 2019) and Practical Geolocation for Electronic Warfare Using MATLAB (Artech House, 2022)
|
|
5
|
+
Author-email: Nicholas O'Donoughue <nodonoug@rand.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: matplotlib
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: scipy
|
|
13
|
+
Requires-Dist: seaborn
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Python Companion to Emitter Detection and Geolocation for Electronic Warfare
|
|
17
|
+
|
|
18
|
+
<img src="graphics/cover_emitterDet.png" height=200 /><img src="graphics/cover_practicalGeo.png" height=200 />
|
|
19
|
+
|
|
20
|
+
This repository is a port of the [MATLAB software companion](https://github.com/nodonoughue/emitter-detection-book/) to *Emitter Detection and Geolocation for Electronic Warfare,* by Nicholas A. O'Donoughue, Artech House, 2019.
|
|
21
|
+
|
|
22
|
+
This repository contains the Python code, released under the MIT License, and when it is complete, it will generate all the figures and implements all the algorithms and many of the performance calculations within the texts *Emitter Detection and Geolocation for Electronic Warfare,* by Nicholas A. O'Donoughue, Artech House, 2019 and *Practical Geolocation for Electronic Warfare using MATLAB,* by Nicholas A. O'Donoughue, Artech House, 2022.
|
|
23
|
+
|
|
24
|
+
The textbooks can be purchased from Artech House directly at the following links: **[Emitter Detection and Geolocation for Electronic Warfare](https://us.artechhouse.com/Emitter-Detection-and-Geolocation-for-Electronic-Warfare-P2291.aspx)**, and **[Practical Geolocation for Electronic Warfare using MATLAB](https://us.artechhouse.com/Practical-Geolocation-for-Electronic-Warfare-Using-MATLAB-P2292.aspx)** Both are also available from Amazon.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### PyPI Install (recommended)
|
|
29
|
+
Use pip to install the package from the PyPI repository
|
|
30
|
+
```
|
|
31
|
+
pip install ewgeo
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
All the tools will be installed and available by importing the `ewgeo` package.
|
|
35
|
+
```
|
|
36
|
+
import ewgeo
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Local Install
|
|
40
|
+
After cloning or downloading the git repository, you can install it locally in any virtual environment.
|
|
41
|
+
|
|
42
|
+
If the path to your downloaded copy of the repository is `<PATH_TO_EWGEO>`, then issue the following commands in a terminal window.
|
|
43
|
+
```
|
|
44
|
+
cd <PATH_TO_EWGEO>
|
|
45
|
+
python3 -m venv .venv
|
|
46
|
+
source .venv/bin/activate
|
|
47
|
+
python3 -m pip install -e .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This repository has been tested with Python 3.12 and 3.13. We recommend using a
|
|
51
|
+
virtual environment for package/dependency handling (the virtual environment
|
|
52
|
+
does not need to be named `.venv`, however).
|
|
53
|
+
|
|
54
|
+
### Dependencies
|
|
55
|
+
|
|
56
|
+
This repository is dependent on the following packages, and was written with Python 3.12.
|
|
57
|
+
+ matplotlib
|
|
58
|
+
+ numpy
|
|
59
|
+
+ scipy
|
|
60
|
+
+ seaborn
|
|
61
|
+
|
|
62
|
+
## Figures
|
|
63
|
+
The **make_figures/** folder contains the code to generate all the figures in the textbook. The subfolder **make_figures/practical_geo** generates figures for the second textbook.
|
|
64
|
+
|
|
65
|
+
To generate all figures, run the file **make_figures.py**. To run figures for an individual chapter, use a command such as the following:
|
|
66
|
+
```python
|
|
67
|
+
import make_figures
|
|
68
|
+
chap1_figs = make_figures.chapter1.make_all_figures()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
The **examples/** folder contains the code to execute each of the examples in the textbook. The subfolder **examples/practical_geo** has examples from the second textbook.
|
|
73
|
+
|
|
74
|
+
## Utilities
|
|
75
|
+
A number of utilities are provided in this repository, under the following modules:
|
|
76
|
+
|
|
77
|
+
+ **ewgeo.aoa** Code to execute angle-of-arrival estimation, as discussed in Chapter 7
|
|
78
|
+
+ **ewgeo.array** Code to execute array-based angle-of-arrival estimation, as discussed in Chapter 8
|
|
79
|
+
+ **ewgeo.atm** Code to model atmospheric loss, as discussed in Appendix Carlo
|
|
80
|
+
+ **ewgeo.detector** Code to model detection performance, as discussed in Chapter 3-4
|
|
81
|
+
+ **ewgeo.fdoa** Code to execute Frequency Difference of Arrival (FDOA) geolocation processing, as discussed in Chapter 12.
|
|
82
|
+
+ **ewgeo.hybrid** Code to execute hybrid geolocation processing, as discussed in Chapter 13.
|
|
83
|
+
+ **ewgeo.noise** Code to model noise power, as discussed in Appendix D.
|
|
84
|
+
+ **ewgeo.prop** Code to model propagation losses, as discussed in Appendix B.
|
|
85
|
+
+ **ewgeo.tdoa** Code to execute Time Difference of Arrival (TDOA) geolocation processing, as discussed in Chapter 11.
|
|
86
|
+
+ **ewgeo.triang** Code to model triangulation from multiple AOA measurements, as discussed in Chapter 10.
|
|
87
|
+
+ **ewgeo.utils** Generic utilities, including numerical solvers used in geolocation algorithms.
|
|
88
|
+
|
|
89
|
+
## Feedback
|
|
90
|
+
Please submit any suggestions, bugs, or comments as issues in this git repository.
|
ewgeo-0.0.1/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Python Companion to Emitter Detection and Geolocation for Electronic Warfare
|
|
2
|
+
|
|
3
|
+
<img src="graphics/cover_emitterDet.png" height=200 /><img src="graphics/cover_practicalGeo.png" height=200 />
|
|
4
|
+
|
|
5
|
+
This repository is a port of the [MATLAB software companion](https://github.com/nodonoughue/emitter-detection-book/) to *Emitter Detection and Geolocation for Electronic Warfare,* by Nicholas A. O'Donoughue, Artech House, 2019.
|
|
6
|
+
|
|
7
|
+
This repository contains the Python code, released under the MIT License, and when it is complete, it will generate all the figures and implements all the algorithms and many of the performance calculations within the texts *Emitter Detection and Geolocation for Electronic Warfare,* by Nicholas A. O'Donoughue, Artech House, 2019 and *Practical Geolocation for Electronic Warfare using MATLAB,* by Nicholas A. O'Donoughue, Artech House, 2022.
|
|
8
|
+
|
|
9
|
+
The textbooks can be purchased from Artech House directly at the following links: **[Emitter Detection and Geolocation for Electronic Warfare](https://us.artechhouse.com/Emitter-Detection-and-Geolocation-for-Electronic-Warfare-P2291.aspx)**, and **[Practical Geolocation for Electronic Warfare using MATLAB](https://us.artechhouse.com/Practical-Geolocation-for-Electronic-Warfare-Using-MATLAB-P2292.aspx)** Both are also available from Amazon.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
### PyPI Install (recommended)
|
|
14
|
+
Use pip to install the package from the PyPI repository
|
|
15
|
+
```
|
|
16
|
+
pip install ewgeo
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
All the tools will be installed and available by importing the `ewgeo` package.
|
|
20
|
+
```
|
|
21
|
+
import ewgeo
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Local Install
|
|
25
|
+
After cloning or downloading the git repository, you can install it locally in any virtual environment.
|
|
26
|
+
|
|
27
|
+
If the path to your downloaded copy of the repository is `<PATH_TO_EWGEO>`, then issue the following commands in a terminal window.
|
|
28
|
+
```
|
|
29
|
+
cd <PATH_TO_EWGEO>
|
|
30
|
+
python3 -m venv .venv
|
|
31
|
+
source .venv/bin/activate
|
|
32
|
+
python3 -m pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This repository has been tested with Python 3.12 and 3.13. We recommend using a
|
|
36
|
+
virtual environment for package/dependency handling (the virtual environment
|
|
37
|
+
does not need to be named `.venv`, however).
|
|
38
|
+
|
|
39
|
+
### Dependencies
|
|
40
|
+
|
|
41
|
+
This repository is dependent on the following packages, and was written with Python 3.12.
|
|
42
|
+
+ matplotlib
|
|
43
|
+
+ numpy
|
|
44
|
+
+ scipy
|
|
45
|
+
+ seaborn
|
|
46
|
+
|
|
47
|
+
## Figures
|
|
48
|
+
The **make_figures/** folder contains the code to generate all the figures in the textbook. The subfolder **make_figures/practical_geo** generates figures for the second textbook.
|
|
49
|
+
|
|
50
|
+
To generate all figures, run the file **make_figures.py**. To run figures for an individual chapter, use a command such as the following:
|
|
51
|
+
```python
|
|
52
|
+
import make_figures
|
|
53
|
+
chap1_figs = make_figures.chapter1.make_all_figures()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
The **examples/** folder contains the code to execute each of the examples in the textbook. The subfolder **examples/practical_geo** has examples from the second textbook.
|
|
58
|
+
|
|
59
|
+
## Utilities
|
|
60
|
+
A number of utilities are provided in this repository, under the following modules:
|
|
61
|
+
|
|
62
|
+
+ **ewgeo.aoa** Code to execute angle-of-arrival estimation, as discussed in Chapter 7
|
|
63
|
+
+ **ewgeo.array** Code to execute array-based angle-of-arrival estimation, as discussed in Chapter 8
|
|
64
|
+
+ **ewgeo.atm** Code to model atmospheric loss, as discussed in Appendix Carlo
|
|
65
|
+
+ **ewgeo.detector** Code to model detection performance, as discussed in Chapter 3-4
|
|
66
|
+
+ **ewgeo.fdoa** Code to execute Frequency Difference of Arrival (FDOA) geolocation processing, as discussed in Chapter 12.
|
|
67
|
+
+ **ewgeo.hybrid** Code to execute hybrid geolocation processing, as discussed in Chapter 13.
|
|
68
|
+
+ **ewgeo.noise** Code to model noise power, as discussed in Appendix D.
|
|
69
|
+
+ **ewgeo.prop** Code to model propagation losses, as discussed in Appendix B.
|
|
70
|
+
+ **ewgeo.tdoa** Code to execute Time Difference of Arrival (TDOA) geolocation processing, as discussed in Chapter 11.
|
|
71
|
+
+ **ewgeo.triang** Code to model triangulation from multiple AOA measurements, as discussed in Chapter 10.
|
|
72
|
+
+ **ewgeo.utils** Generic utilities, including numerical solvers used in geolocation algorithms.
|
|
73
|
+
|
|
74
|
+
## Feedback
|
|
75
|
+
Please submit any suggestions, bugs, or comments as issues in this git repository.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ewgeo"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Nicholas O'Donoughue", email="nodonoug@rand.org" },
|
|
10
|
+
]
|
|
11
|
+
description = "Python code companion to Emitter Detection and Geolocation for Electronic Warfare (Artech House, 2019) and Practical Geolocation for Electronic Warfare Using MATLAB (Artech House, 2022)"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">= 3.12"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"matplotlib",
|
|
16
|
+
"numpy",
|
|
17
|
+
"scipy",
|
|
18
|
+
"seaborn",
|
|
19
|
+
]
|
|
20
|
+
license = "MIT"
|
|
21
|
+
license-files = ["LICEN[CS]E*"]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools]
|
|
24
|
+
package-dir = {"" = "src"}
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
ewgeo-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ewgeo.utils import sinc_derivative
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def make_gain_functions(aperture_type, d_lam, psi_0):
|
|
7
|
+
"""
|
|
8
|
+
Generate function handles for the gain pattern (g) and gradient (g_dot),
|
|
9
|
+
given the specified aperture type, aperture size, and mechanical steering
|
|
10
|
+
angle.
|
|
11
|
+
|
|
12
|
+
Ported from MATLAB Code
|
|
13
|
+
|
|
14
|
+
Nicholas O'Donoughue
|
|
15
|
+
9 January 2021
|
|
16
|
+
|
|
17
|
+
:param aperture_type: String indicating the type of aperture requested. Supports 'omni', 'adcock', 'rectangular'
|
|
18
|
+
:param d_lam: Aperture length, in units of wavelength (d/lambda)
|
|
19
|
+
:param psi_0: Mechanical steering angle (in radians) of the array [default=0]
|
|
20
|
+
:return g: Function handle to the antenna pattern g(psi), for psi in radians
|
|
21
|
+
:return g_dot: Function handle to the gradient of the antenna pattern, g_dot(psi), for psi in radians.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# type = 'adcock' or 'Rectangular'
|
|
25
|
+
# params
|
|
26
|
+
# d_lam = baseline (in wavelengths)
|
|
27
|
+
# psi_0 = central angle
|
|
28
|
+
|
|
29
|
+
# Define all the possible functions
|
|
30
|
+
def g_omni(psi):
|
|
31
|
+
return np.ones_like(psi)
|
|
32
|
+
|
|
33
|
+
def g_dot_omni(psi):
|
|
34
|
+
return np.zeros_like(psi)
|
|
35
|
+
|
|
36
|
+
def g_adcock(psi):
|
|
37
|
+
return 2*np.sin(np.pi * d_lam * np.cos(psi-psi_0))
|
|
38
|
+
|
|
39
|
+
def g_dot_adcock(psi):
|
|
40
|
+
return -2*np.pi*d_lam*np.sin(psi-psi_0)*np.cos(np.pi*d_lam*np.cos(psi-psi_0))
|
|
41
|
+
|
|
42
|
+
def g_rect(psi):
|
|
43
|
+
# In the text, the function is sinc((psi-psi_0)*d/2*lam), but the sinc function is defined sin(x)/x
|
|
44
|
+
# In numpy, the sinc function is sin(pi*x)/(pi*x), thus we should divide the argument to numpy.sinc by pi
|
|
45
|
+
return np.abs(np.sinc((psi-psi_0)*d_lam/np.pi)) # sinc includes implicit pi
|
|
46
|
+
|
|
47
|
+
def g_dot_rect(psi):
|
|
48
|
+
return sinc_derivative((psi - psi_0) * d_lam) * d_lam
|
|
49
|
+
|
|
50
|
+
switcher = {'omni': (g_omni, g_dot_omni),
|
|
51
|
+
'adcock': (g_adcock, g_dot_adcock),
|
|
52
|
+
'rectangular': (g_rect, g_dot_rect)}
|
|
53
|
+
|
|
54
|
+
result = switcher.get(aperture_type.lower())
|
|
55
|
+
|
|
56
|
+
return result[0], result[1]
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from . import make_gain_functions
|
|
5
|
+
from ewgeo.utils.unit_conversions import db_to_lin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def crlb(snr, num_samples, g, g_dot, psi_samples, psi_true):
|
|
9
|
+
"""
|
|
10
|
+
Computes the CRLB for a directional antenna with amplitude measurements taken at a series of angles. Supports M
|
|
11
|
+
measurements from each of N different angles.
|
|
12
|
+
|
|
13
|
+
If there are multiple true angles of arrival provided (psi_true), then the CRLB is computed independently for each
|
|
14
|
+
one.
|
|
15
|
+
|
|
16
|
+
Ported from MATLAB Code.
|
|
17
|
+
|
|
18
|
+
Nicholas O'Donoughue
|
|
19
|
+
14 January 2021
|
|
20
|
+
|
|
21
|
+
:param snr: Signal-to-Noise ratio [dB]
|
|
22
|
+
:param num_samples: Number of samples for each antenna position
|
|
23
|
+
:param g: Function handle to g(psi)
|
|
24
|
+
:param g_dot: Function handle to g_dot(psi)
|
|
25
|
+
:param psi_samples: The sampled steering angles (radians)
|
|
26
|
+
:param psi_true: The true angle of arrival (radians)
|
|
27
|
+
:return crlb: Lower bound on the Mean Squared Error of an unbiased estimation of psi (radians)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Convert SNR from dB to linear units
|
|
31
|
+
snr_lin = db_to_lin(snr)
|
|
32
|
+
|
|
33
|
+
# Evaluate the antenna pattern and antenna gradient at each of the steering angles sampled.
|
|
34
|
+
g_vec = np.array([g(psi-psi_true) for psi in psi_samples])
|
|
35
|
+
g_dot_vec = np.array([g_dot(psi-psi_true) for psi in psi_samples])
|
|
36
|
+
|
|
37
|
+
# Pre-compute steering vector inner products
|
|
38
|
+
g_g = np.sum(g_vec * g_vec, axis=0)
|
|
39
|
+
g_dot_g = np.sum(g_vec * g_dot_vec, axis=0)
|
|
40
|
+
g_dot_g_dot = np.sum(g_dot_vec * g_dot_vec, axis=0)
|
|
41
|
+
|
|
42
|
+
# Compute CRLB for each true angle theta
|
|
43
|
+
jacobian = 2 * num_samples * snr_lin * (g_dot_g_dot - g_dot_g ** 2 / g_g) # Eq 7.25
|
|
44
|
+
return 1./jacobian # 1 x num_angles
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def compute_df(s, psi_samples, g, psi_res=0.1, min_psi=-np.pi, max_psi=np.pi):
|
|
48
|
+
"""
|
|
49
|
+
Computes an estimate of the angle of arrival psi (in radians) for a set of amplitude measurements s, taken at
|
|
50
|
+
various steering angles psi_samples
|
|
51
|
+
|
|
52
|
+
Ported from MATLAB Code.
|
|
53
|
+
|
|
54
|
+
Nicholas O'Donoughue
|
|
55
|
+
14 January 2021
|
|
56
|
+
|
|
57
|
+
:param s: Set of num_samples measurements taken at each of num_steering steering angles.
|
|
58
|
+
:param psi_samples: Steering angles at which measurements were taken [radians]
|
|
59
|
+
:param g: Function handle to gain equation, g(psi)
|
|
60
|
+
:param psi_res: Desired resolution for output estimate [default = .1]
|
|
61
|
+
:param min_psi: Lower bound on valid region for psi [default = -pi]
|
|
62
|
+
:param max_psi: Upper bound on valid region for psi [default = pi]
|
|
63
|
+
:return: Estimated angle of arrival [radians]
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# Initialize the outer loop to .1 radians
|
|
67
|
+
this_psi_res = .1
|
|
68
|
+
psi = 0. # Initialize output
|
|
69
|
+
|
|
70
|
+
# Ensure at least one loop
|
|
71
|
+
psi_res = min(psi_res, this_psi_res)
|
|
72
|
+
|
|
73
|
+
while this_psi_res >= psi_res:
|
|
74
|
+
psi_vec = np.arange(start=min_psi, stop=max_psi + this_psi_res, step=this_psi_res) # Set up search vector
|
|
75
|
+
|
|
76
|
+
# Find the difference between each possible AoA (psi_vec) and the measurement steering angles
|
|
77
|
+
psi_diff = psi_samples[:, np.newaxis] - psi_vec[np.newaxis, :]
|
|
78
|
+
|
|
79
|
+
# Compute the expected gain pattern for each candidate AoA value
|
|
80
|
+
g_vec = g(psi_diff)
|
|
81
|
+
|
|
82
|
+
# Find the candidate AoA value that minimizes the MSE between the
|
|
83
|
+
# expected and received gain patterns.
|
|
84
|
+
sse = np.sum(np.absolute(s[:, :, np.newaxis]-g_vec[:, np.newaxis, :])**2, axis=(0, 1))
|
|
85
|
+
idx_opt = np.argmin(sse)
|
|
86
|
+
psi = psi_vec[idx_opt]
|
|
87
|
+
|
|
88
|
+
# Set up the bounds and resolution for the next iteration
|
|
89
|
+
this_psi_res /= 10
|
|
90
|
+
idx_min = max(0, idx_opt-4)
|
|
91
|
+
idx_max = min(len(psi_vec)-1, idx_opt+4)
|
|
92
|
+
min_psi = psi_vec[idx_min]
|
|
93
|
+
max_psi = psi_vec[idx_max]
|
|
94
|
+
|
|
95
|
+
return psi
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def run_example(mc_params=None):
|
|
99
|
+
"""
|
|
100
|
+
Example evaluation of an Adcock and Rectangular-aperture DF receiver
|
|
101
|
+
|
|
102
|
+
Ported from MATLAB code.
|
|
103
|
+
|
|
104
|
+
Nicholas O'Donoughue
|
|
105
|
+
14 January 2021
|
|
106
|
+
|
|
107
|
+
:param mc_params: Optional struct to control Monte Carlo trial size
|
|
108
|
+
:return: None
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# ================================ Adcock Test Script ================================
|
|
112
|
+
# Create the antenna pattern generating function
|
|
113
|
+
# --- NOTE --- g,g_dot take radian inputs (psi, not theta)
|
|
114
|
+
d_lam = .25
|
|
115
|
+
[g, g_dot] = make_gain_functions(aperture_type='adcock', d_lam=d_lam, psi_0=0.)
|
|
116
|
+
|
|
117
|
+
# Generate the angular samples and true gain values
|
|
118
|
+
th_true = 5. # degrees
|
|
119
|
+
psi_true = np.deg2rad(th_true) # radians
|
|
120
|
+
psi_res = .001 # desired resolution from multi-stage search directional_df() for details.
|
|
121
|
+
|
|
122
|
+
num_angles = 10 # Number of angular samples
|
|
123
|
+
th = np.linspace(start=-180., stop=180., num=num_angles, endpoint=False) # evenly spaced across unit circle
|
|
124
|
+
psi = np.deg2rad(th)
|
|
125
|
+
x = g(psi-psi_true) # Actual gain values
|
|
126
|
+
|
|
127
|
+
# Set up the parameter sweep
|
|
128
|
+
num_samples_vec = np.array([1, 10, 100]) # Number of temporal samples at each antenna test point
|
|
129
|
+
snr_db_vec = np.arange(start=-20, step=2, stop=20+2) # signal-to-noise ratio
|
|
130
|
+
num_monte_carlo = 1000 # number of monte carlo trials at each parameter setting
|
|
131
|
+
if mc_params is not None:
|
|
132
|
+
num_monte_carlo = max(int(num_monte_carlo/mc_params['monte_carlo_decimation']),mc_params['min_num_monte_carlo'])
|
|
133
|
+
|
|
134
|
+
# Set up output variables
|
|
135
|
+
out_shp = [np.size(num_samples_vec), np.size(snr_db_vec)]
|
|
136
|
+
rmse_psi = np.zeros(shape=out_shp)
|
|
137
|
+
crlb_psi = np.zeros(shape=out_shp)
|
|
138
|
+
|
|
139
|
+
# Loop over parameters
|
|
140
|
+
print('Executing Adcock Monte Carlo sweep...')
|
|
141
|
+
for idx_num_samples, num_samples in enumerate(num_samples_vec.tolist()):
|
|
142
|
+
this_num_monte_carlo = num_monte_carlo / num_samples
|
|
143
|
+
print('\tnum_samples={}'.format(num_samples))
|
|
144
|
+
|
|
145
|
+
# Generate Monte Carlo Noise with unit power
|
|
146
|
+
noise_base = [np.random.normal(size=(num_angles, num_samples)) for _ in np.arange(this_num_monte_carlo)]
|
|
147
|
+
|
|
148
|
+
# Loop over SNR levels
|
|
149
|
+
for idx_snr, snr_db in enumerate(snr_db_vec.tolist()):
|
|
150
|
+
print('.')
|
|
151
|
+
|
|
152
|
+
# Compute noise power, scale base noise
|
|
153
|
+
noise_amp = db_to_lin(-snr_db/2)
|
|
154
|
+
|
|
155
|
+
# Generate noisy measurement
|
|
156
|
+
y = [x+noise_amp*this_noise for this_noise in noise_base]
|
|
157
|
+
|
|
158
|
+
# Estimate angle of arrival for each Monte Carlo trial
|
|
159
|
+
psi_est = np.array([compute_df(this_y, psi, g, psi_res, min(psi), max(psi)) for this_y in y])
|
|
160
|
+
|
|
161
|
+
# Compute RMS Error
|
|
162
|
+
rmse_psi[idx_num_samples, idx_snr] = np.sqrt(np.mean((psi_est - psi_true) ** 2))
|
|
163
|
+
|
|
164
|
+
# Compute CRLB for RMS Error
|
|
165
|
+
crlb_psi[idx_num_samples, idx_snr] = crlb(snr_db, num_samples, g, g_dot, psi, psi_true)
|
|
166
|
+
|
|
167
|
+
print('done.')
|
|
168
|
+
|
|
169
|
+
_, _ = plt.subplots()
|
|
170
|
+
|
|
171
|
+
for idx_num_samples, this_num_samples in enumerate(num_samples_vec):
|
|
172
|
+
crlb_label = 'CRLB, M={}'.format(this_num_samples)
|
|
173
|
+
mc_label = 'Simulation Result, M={}'.format(this_num_samples)
|
|
174
|
+
|
|
175
|
+
# Plot the MC and CRLB results for this number of samples
|
|
176
|
+
handle1 = plt.semilogy(snr_db_vec, np.rad2deg(np.sqrt(crlb_psi[idx_num_samples, :])), label=crlb_label)
|
|
177
|
+
plt.semilogy(snr_db_vec, np.rad2deg(rmse_psi[idx_num_samples, :]), color=handle1[0].get_color(),
|
|
178
|
+
style='--', label=mc_label)
|
|
179
|
+
|
|
180
|
+
plt.xlabel(r'$\xi$ [dB]')
|
|
181
|
+
plt.ylabel('RMSE [deg]')
|
|
182
|
+
plt.title('Adcock DF Performance')
|
|
183
|
+
plt.legend(loc='lower left')
|
|
184
|
+
|
|
185
|
+
# ============================= Reflector/Array Test Script =============================
|
|
186
|
+
# Create the antenna pattern generating function
|
|
187
|
+
aperture_size_wavelengths = 5
|
|
188
|
+
[g, g_dot] = make_gain_functions(aperture_type='rectangular', d_lam=aperture_size_wavelengths, psi_0=0.)
|
|
189
|
+
|
|
190
|
+
# Generate the angular samples and true gain values
|
|
191
|
+
th_true = 5.
|
|
192
|
+
psi_true = np.deg2rad(th_true)
|
|
193
|
+
num_angles = 36 # number of samples
|
|
194
|
+
th = np.linspace(start=-180, stop=180, num=num_angles, endpoint=False) # evenly spaced across unit circle
|
|
195
|
+
psi = np.deg2rad(th)
|
|
196
|
+
x = g(psi - psi_true) # Actual gain values
|
|
197
|
+
psi_res = .001 # desired resolution from multi-stage search, see directional_df for details.
|
|
198
|
+
|
|
199
|
+
# Set up the parameter sweep
|
|
200
|
+
num_samples_vec = np.array([1, 10, 100]) # Number of temporal samples at each antenna test point
|
|
201
|
+
snr_db_vec = np.arange(start=-20, step=2, stop=20 + 2) # signal-to-noise ratio
|
|
202
|
+
num_monte_carlo = 1000 # number of monte carlo trials at each parameter setting
|
|
203
|
+
if mc_params is not None:
|
|
204
|
+
num_monte_carlo = max(int(num_monte_carlo/mc_params['monte_carlo_decimation']),mc_params['min_num_monte_carlo'])
|
|
205
|
+
|
|
206
|
+
# Set up output variables
|
|
207
|
+
out_shp = [np.size(num_samples_vec), np.size(snr_db_vec)]
|
|
208
|
+
rmse_psi = np.zeros(shape=out_shp)
|
|
209
|
+
crlb_psi = np.zeros(shape=out_shp)
|
|
210
|
+
|
|
211
|
+
# Loop over parameters
|
|
212
|
+
print('Executing Adcock Monte Carlo sweep...')
|
|
213
|
+
for idx_num_samples, num_samples in enumerate(num_samples_vec.tolist()):
|
|
214
|
+
this_num_monte_carlo = num_monte_carlo / num_samples
|
|
215
|
+
print('\tnum_samples={}'.format(num_samples))
|
|
216
|
+
|
|
217
|
+
# Generate Monte Carlo Noise with unit power
|
|
218
|
+
noise_base = [np.random.normal(size=(num_angles, num_samples)) for _ in np.arange(this_num_monte_carlo)]
|
|
219
|
+
|
|
220
|
+
# Loop over SNR levels
|
|
221
|
+
for idx_snr, snr_db in enumerate(snr_db_vec.tolist()):
|
|
222
|
+
print('.')
|
|
223
|
+
|
|
224
|
+
# Compute noise power, scale base noise
|
|
225
|
+
noise_amp = db_to_lin(-snr_db/2)
|
|
226
|
+
|
|
227
|
+
# Generate noisy measurement
|
|
228
|
+
y = [x + noise_amp * this_noise for this_noise in noise_base]
|
|
229
|
+
|
|
230
|
+
# Estimate angle of arrival for each Monte Carlo trial
|
|
231
|
+
psi_est = np.array([compute_df(this_y, psi, g, psi_res, min(psi), max(psi)) for this_y in y])
|
|
232
|
+
|
|
233
|
+
# Compute RMS Error
|
|
234
|
+
rmse_psi[idx_num_samples, idx_snr] = np.sqrt(np.mean((psi_est - psi_true) ** 2))
|
|
235
|
+
|
|
236
|
+
# Compute CRLB for RMS Error
|
|
237
|
+
crlb_psi[idx_num_samples, idx_snr] = crlb(snr_db, num_samples, g, g_dot, psi, psi_true)
|
|
238
|
+
|
|
239
|
+
print('done.')
|
|
240
|
+
|
|
241
|
+
_, _ = plt.subplots()
|
|
242
|
+
|
|
243
|
+
for idx_num_samples, this_num_samples in enumerate(num_samples_vec):
|
|
244
|
+
crlb_label = 'CRLB, M={}'.format(this_num_samples)
|
|
245
|
+
mc_label = 'Simulation Result, M={}'.format(this_num_samples)
|
|
246
|
+
|
|
247
|
+
# Plot the MC and CRLB results for this number of samples
|
|
248
|
+
handle1 = plt.semilogy(snr_db_vec, np.rad2deg(np.sqrt(crlb_psi[idx_num_samples, :])), label=crlb_label)
|
|
249
|
+
plt.semilogy(snr_db_vec, np.rad2deg(rmse_psi[idx_num_samples, :]), color=handle1[0].get_color(),
|
|
250
|
+
style='--', label=mc_label)
|
|
251
|
+
|
|
252
|
+
plt.xlabel(r'$\xi$ [dB]')
|
|
253
|
+
plt.ylabel('RMSE [deg]')
|
|
254
|
+
plt.title('Rectangular Array DF Performance')
|
|
255
|
+
plt.legend(loc='lower left')
|