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.
Files changed (80) hide show
  1. ewgeo-0.0.1/LICENSE +21 -0
  2. ewgeo-0.0.1/PKG-INFO +90 -0
  3. ewgeo-0.0.1/README.md +75 -0
  4. ewgeo-0.0.1/pyproject.toml +27 -0
  5. ewgeo-0.0.1/setup.cfg +4 -0
  6. ewgeo-0.0.1/src/ewgeo/aoa/__init__.py +5 -0
  7. ewgeo-0.0.1/src/ewgeo/aoa/aoa.py +56 -0
  8. ewgeo-0.0.1/src/ewgeo/aoa/directional.py +255 -0
  9. ewgeo-0.0.1/src/ewgeo/aoa/doppler.py +233 -0
  10. ewgeo-0.0.1/src/ewgeo/aoa/interferometer.py +149 -0
  11. ewgeo-0.0.1/src/ewgeo/aoa/watson_watt.py +154 -0
  12. ewgeo-0.0.1/src/ewgeo/array_df/__init__.py +3 -0
  13. ewgeo-0.0.1/src/ewgeo/array_df/model.py +97 -0
  14. ewgeo-0.0.1/src/ewgeo/array_df/perf.py +91 -0
  15. ewgeo-0.0.1/src/ewgeo/array_df/solvers.py +157 -0
  16. ewgeo-0.0.1/src/ewgeo/atm/__init__.py +3 -0
  17. ewgeo-0.0.1/src/ewgeo/atm/model.py +348 -0
  18. ewgeo-0.0.1/src/ewgeo/atm/reference.py +239 -0
  19. ewgeo-0.0.1/src/ewgeo/atm/test.py +140 -0
  20. ewgeo-0.0.1/src/ewgeo/detector/__init__.py +2 -0
  21. ewgeo-0.0.1/src/ewgeo/detector/squareLaw.py +204 -0
  22. ewgeo-0.0.1/src/ewgeo/detector/xcorr.py +176 -0
  23. ewgeo-0.0.1/src/ewgeo/fdoa/__init__.py +3 -0
  24. ewgeo-0.0.1/src/ewgeo/fdoa/model.py +637 -0
  25. ewgeo-0.0.1/src/ewgeo/fdoa/perf.py +118 -0
  26. ewgeo-0.0.1/src/ewgeo/fdoa/system.py +157 -0
  27. ewgeo-0.0.1/src/ewgeo/hybrid/__init__.py +3 -0
  28. ewgeo-0.0.1/src/ewgeo/hybrid/model.py +535 -0
  29. ewgeo-0.0.1/src/ewgeo/hybrid/perf.py +66 -0
  30. ewgeo-0.0.1/src/ewgeo/hybrid/system.py +785 -0
  31. ewgeo-0.0.1/src/ewgeo/noise/__init__.py +1 -0
  32. ewgeo-0.0.1/src/ewgeo/noise/model.py +189 -0
  33. ewgeo-0.0.1/src/ewgeo/prop/__init__.py +1 -0
  34. ewgeo-0.0.1/src/ewgeo/prop/model.py +228 -0
  35. ewgeo-0.0.1/src/ewgeo/tdoa/__init__.py +4 -0
  36. ewgeo-0.0.1/src/ewgeo/tdoa/model.py +566 -0
  37. ewgeo-0.0.1/src/ewgeo/tdoa/perf.py +55 -0
  38. ewgeo-0.0.1/src/ewgeo/tdoa/solvers.py +187 -0
  39. ewgeo-0.0.1/src/ewgeo/tdoa/system.py +151 -0
  40. ewgeo-0.0.1/src/ewgeo/tracker/__init__.py +15 -0
  41. ewgeo-0.0.1/src/ewgeo/tracker/association.py +613 -0
  42. ewgeo-0.0.1/src/ewgeo/tracker/deleter.py +18 -0
  43. ewgeo-0.0.1/src/ewgeo/tracker/initiator.py +33 -0
  44. ewgeo-0.0.1/src/ewgeo/tracker/measurement.py +203 -0
  45. ewgeo-0.0.1/src/ewgeo/tracker/promoter.py +32 -0
  46. ewgeo-0.0.1/src/ewgeo/tracker/states.py +216 -0
  47. ewgeo-0.0.1/src/ewgeo/tracker/track.py +171 -0
  48. ewgeo-0.0.1/src/ewgeo/tracker/tracker.py +244 -0
  49. ewgeo-0.0.1/src/ewgeo/tracker/transition.py +355 -0
  50. ewgeo-0.0.1/src/ewgeo/triang/__init__.py +4 -0
  51. ewgeo-0.0.1/src/ewgeo/triang/model.py +471 -0
  52. ewgeo-0.0.1/src/ewgeo/triang/perf.py +48 -0
  53. ewgeo-0.0.1/src/ewgeo/triang/solvers.py +133 -0
  54. ewgeo-0.0.1/src/ewgeo/triang/system.py +209 -0
  55. ewgeo-0.0.1/src/ewgeo/utils/__init__.py +12 -0
  56. ewgeo-0.0.1/src/ewgeo/utils/constants.py +24 -0
  57. ewgeo-0.0.1/src/ewgeo/utils/constraints.py +568 -0
  58. ewgeo-0.0.1/src/ewgeo/utils/coordinates.py +647 -0
  59. ewgeo-0.0.1/src/ewgeo/utils/covariance.py +591 -0
  60. ewgeo-0.0.1/src/ewgeo/utils/errors.py +302 -0
  61. ewgeo-0.0.1/src/ewgeo/utils/geo.py +325 -0
  62. ewgeo-0.0.1/src/ewgeo/utils/perf.py +118 -0
  63. ewgeo-0.0.1/src/ewgeo/utils/search_space.py +284 -0
  64. ewgeo-0.0.1/src/ewgeo/utils/solvers.py +436 -0
  65. ewgeo-0.0.1/src/ewgeo/utils/system.py +1343 -0
  66. ewgeo-0.0.1/src/ewgeo/utils/unit_conversions.py +158 -0
  67. ewgeo-0.0.1/src/ewgeo/utils/utils.py +721 -0
  68. ewgeo-0.0.1/src/ewgeo.egg-info/PKG-INFO +90 -0
  69. ewgeo-0.0.1/src/ewgeo.egg-info/SOURCES.txt +78 -0
  70. ewgeo-0.0.1/src/ewgeo.egg-info/dependency_links.txt +1 -0
  71. ewgeo-0.0.1/src/ewgeo.egg-info/requires.txt +4 -0
  72. ewgeo-0.0.1/src/ewgeo.egg-info/top_level.txt +1 -0
  73. ewgeo-0.0.1/tests/test_aoa_gain_functions.py +44 -0
  74. ewgeo-0.0.1/tests/test_constraints.py +100 -0
  75. ewgeo-0.0.1/tests/test_coordinates.py +281 -0
  76. ewgeo-0.0.1/tests/test_covariance.py +332 -0
  77. ewgeo-0.0.1/tests/test_crlb.py +95 -0
  78. ewgeo-0.0.1/tests/test_errors.py +54 -0
  79. ewgeo-0.0.1/tests/test_geo.py +114 -0
  80. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from .aoa import *
2
+ from . import directional
3
+ from . import doppler
4
+ from . import interferometer
5
+ from . import watson_watt
@@ -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')