diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8dfcd93a..6cd63865 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,33 +1,14 @@ FROM ubuntu:latest -# Set environment variables for non-interactive installation and set micromamba paths -ENV DEBIAN_FRONTEND=noninteractive \ - MAMBA_ROOT_PREFIX=/opt/micromamba \ - PATH=/opt/micromamba/bin:$PATH \ - MAMBA_DOCKERFILE_ACTIVATE=1 +# Set environment variables for non-interactive installation +ENV DEBIAN_FRONTEND=noninteractive -# Install system dependencies and micromamba -RUN apt-get update && apt-get -y upgrade && \ - apt-get install -y --no-install-recommends \ - curl \ - ca-certificates \ - bzip2 && \ - curl -L https://micromamba.snakepit.net/api/micromamba/linux-64/latest | \ - tar -xvj -C /usr/local/bin --strip-components=1 bin/micromamba - -# Initialize micromamba for all future shells (important) -RUN echo 'eval "$(micromamba shell hook --shell bash)"' >> /etc/bash.bashrc +# Install ca-certificates for HTTPS support in install.sh +RUN apt-get update && \ + apt-get install -y --no-install-recommends ca-certificates && \ + rm -rf /var/lib/apt/lists/* -# Clean up micromamba install tools and continue with other packages -RUN apt-get purge -y curl bzip2 && \ - apt-get autoremove -y && \ - apt-get clean - -# Copy the install script into the container COPY install.sh /tmp/install.sh -# Run the script with loggin on and remove it in a single layer -RUN chmod +x /tmp/install.sh && /tmp/install.sh || (cat /tmp/install.log && exit 1) && \ - rm -f /tmp/install.sh /tmp/install.log && \ - rm -rf /var/lib/apt/lists/* - \ No newline at end of file +RUN chmod +x /tmp/install.sh && /tmp/install.sh && \ + rm -f /tmp/install.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 757b2524..c3d2b5e5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,6 +4,6 @@ "service": "dev", "workspaceFolder": "/workspace", "overrideCommand": true, - "postCreateCommand": "git config --global user.name \"${GIT_AUTHOR_NAME:-Your Name}\" && git config --global user.email \"${GIT_AUTHOR_EMAIL:-you@example.com}\" && /workspace/tools/setup-tools-envs.sh && /workspace/build.sh cmake", + "postCreateCommand": "git config --global user.name \"${GIT_AUTHOR_NAME:-Your Name}\" && git config --global user.email \"${GIT_AUTHOR_EMAIL:-you@example.com}\"", "shutdownAction": "stopCompose" } \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 695c91b3..fc827403 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -9,7 +9,4 @@ services: dockerfile: .devcontainer/Dockerfile volumes: - ..:/workspace:cached - - micromamba-data:/opt/micromamba command: sleep infinity -volumes: - micromamba-data: diff --git a/CMakeLists.txt b/CMakeLists.txt index 800be6c9..d0aa8e1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,13 +134,29 @@ add_library(${STB_IMAGE} STATIC ${STB_IMAGE_SRC}) target_compile_definitions(${STB_IMAGE} PRIVATE STB_IMAGE_IMPLEMENTATION) target_include_directories(${STB_IMAGE} PUBLIC $) +##### Eigen ##### + +# Disable Eigen's own tests and docs to speed up configuration +set(BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(EIGEN_BUILD_DOC OFF CACHE BOOL "" FORCE) +set(EIGEN_BUILD_PKGCONFIG OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + eigen + GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git + GIT_TAG 3.4.0 + GIT_SHALLOW ON + EXCLUDE_FROM_ALL +) +FetchContent_MakeAvailable(eigen) + # Set the source libraries set(SRC_LIBS ${STB_IMAGE}) ##### FOUND Library ##### add_library(found_lib STATIC ${FILTERED_SRC}) -target_link_libraries(found_lib PRIVATE ${STB_IMAGE}) +target_link_libraries(found_lib PUBLIC Eigen3::Eigen PRIVATE ${STB_IMAGE}) target_include_directories(found_lib PUBLIC $ @@ -220,6 +236,7 @@ add_executable(found-test EXCLUDE_FROM_ALL ${TEST} ${FILTERED_SRC}) target_compile_definitions(found-test PRIVATE ENABLE_LOGGING LOGGING_LEVEL=INFO) target_compile_options(found-test PRIVATE + -Wdouble-promotion -Werror --coverage -pthread $<$>:-fsanitize=address> @@ -228,6 +245,7 @@ target_compile_options(found-test PRIVATE target_link_libraries(found-test PRIVATE --coverage ${TEST_LIBS} + Eigen3::Eigen $<$>:-fsanitize=address> $<$>:-fomit-frame-pointer>) set_target_properties(found-test PROPERTIES diff --git a/Makefile b/Makefile index 801e74d9..1973f41f 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,14 @@ DOXYGEN_AWESOME_URL := https://github.com/jothepro/$(DOXYGEN_AWESOME)/archive/re DOXYGEN_AWESOME_ZIP := $(CACHE_DIR)/v$(DOXYGEN_AWESOME_VERSION).tar.gz DOXYGEN_AWESOME_ARTIFACT := $(CACHE_DIR)/$(DOXYGEN_AWESOME)-$(DOXYGEN_AWESOME_VERSION) +# Define the Eigen library +EIGEN := eigen +EIGEN_VERSION := 3.4.0 +EIGEN_URL := https://gitlab.com/libeigen/eigen/-/archive/$(EIGEN_VERSION)/eigen-$(EIGEN_VERSION).tar.gz +EIGEN_CACHE_ARTIFACT := $(CACHE_DIR)/eigen-$(EIGEN_VERSION).tar.gz +EIGEN_CACHE_DIR := $(CACHE_DIR)/eigen-$(EIGEN_VERSION) +EIGEN_DIR := $(BUILD_LIBRARY_SRC_DIR)/eigen-$(EIGEN_VERSION) + # Define all source and test code SRC := $(shell find $(SRC_DIR) -name "*.cpp") @@ -88,8 +96,8 @@ endif LOGGING_MACROS_TEST := -DENABLE_LOGGING -DLOGGING_LEVEL=INFO # Compiler flags -LIBS := $(SRC_LIBS) -I$(BUILD_LIBRARY_SRC_DIR) -LIBS_TEST := $(TEST_LIBS) -I$(GTEST_DIR)/$(GTEST)/include -I$(GTEST_DIR)/googlemock/include -pthread +LIBS := $(SRC_LIBS) -I$(BUILD_LIBRARY_SRC_DIR) -isystem $(EIGEN_DIR) +LIBS_TEST := $(TEST_LIBS) -isystem $(EIGEN_DIR) -I$(GTEST_DIR)/$(GTEST)/include -I$(GTEST_DIR)/googlemock/include -pthread DEBUG_FLAGS := -ggdb -fno-omit-frame-pointer COVERAGE_FLAGS := --coverage CXXFLAGS := $(CXXFLAGS) -Wall -Wextra -Wno-missing-field-initializers -Werror -pedantic --std=gnu++17 -MMD $(LIBS) $(FOUND_FLOAT_MODE_MACRO) @@ -154,7 +162,7 @@ all: $(COMPILE_SETUP_TARGET) \ $(DOXYGEN_TARGET) \ # The build setup target (sets up appropriate directories) -$(COMPILE_SETUP_TARGET): compile_setup_message $(BUILD_DIR) $(STB_IMAGE_DIR) +$(COMPILE_SETUP_TARGET): compile_setup_message $(BUILD_DIR) $(STB_IMAGE_DIR) $(EIGEN_DIR) $(BUILD_DIR): mkdir -p $(BUILD_DIR) mkdir -p $(BUILD_DOCUMENTATION_DIR) @@ -173,11 +181,18 @@ $(STB_IMAGE_CACHE_ARTIFACT): echo '#define STB_IMAGE_IMPLEMENTATION\n#include "stb_image/stb_image.h"' > $(STB_IMAGE_CACHE_ARTIFACT) $(CXX) -I$(CACHE_DIR) -c $(STB_IMAGE_CACHE_ARTIFACT) -o $(STB_IMAGE_CACHE_DIR)/$(STB_IMAGE).o # Exclude CXXFLAGS because we know its fine +# Eigen header-only library (download and extract) +$(EIGEN_DIR): $(EIGEN_CACHE_ARTIFACT) $(BUILD_LIBRARY_SRC_DIR) + tar -xzf $(EIGEN_CACHE_ARTIFACT) -C $(BUILD_LIBRARY_SRC_DIR) +$(EIGEN_CACHE_ARTIFACT): + mkdir -p $(CACHE_DIR) + wget $(EIGEN_URL) -P $(CACHE_DIR) + # The compile target $(COMPILE_TARGET): $(COMPILE_SETUP_TARGET) compile_message $(BIN) -$(BIN): $(SRC_OBJS) $(BIN_DIR) $(STB_IMAGE_DIR) +$(BIN): $(SRC_OBJS) $(BIN_DIR) $(STB_IMAGE_DIR) $(EIGEN_DIR) $(CXX) $(OPTIMIZATION) $(CXXFLAGS) -o $(BIN) $(SRC_OBJS) $(LDFLAGS) -$(BUILD_SRC_DIR)/%.o: $(SRC_DIR)/%.cpp $(STB_IMAGE_DIR) +$(BUILD_SRC_DIR)/%.o: $(SRC_DIR)/%.cpp $(STB_IMAGE_DIR) $(EIGEN_DIR) mkdir -p $(@D) $(CXX) $(OPTIMIZATION) $(CXXFLAGS) -c $< -o $@ compile_message: diff --git a/TLS-data - Copy.csv:Zone.Identifier b/TLS-data - Copy.csv:Zone.Identifier new file mode 100644 index 00000000..d6c1ec68 Binary files /dev/null and b/TLS-data - Copy.csv:Zone.Identifier differ diff --git a/TLS-data.csv b/TLS-data.csv new file mode 100644 index 00000000..86eebb2b --- /dev/null +++ b/TLS-data.csv @@ -0,0 +1,2481 @@ +rows,cols,noise,error,expected mag +7,3,0,0,903.865 +7,3,0.0166667,5.70964e-05,981.062 +7,3,0.0333333,7.86071e-05,1089.56 +7,3,0.05,7.60628e-05,530.375 +7,3,0.0666667,0.000170888,721.96 +7,3,0.0833333,0.000114535,909.304 +7,3,0.1,0.000117907,634.393 +7,3,0.116667,0.000252728,606.367 +7,3,0.133333,0.000200041,1038.39 +7,3,0.15,0.000293423,1102.96 +7,3,0.166667,0.000322102,473.171 +7,3,0.183333,0.000235509,148.818 +7,3,0.2,0.000277905,776.234 +7,3,0.216667,0.000552486,158.773 +7,3,0.233333,0.000392238,329.207 +7,3,0.25,0.000379853,839.861 +7,3,0.266667,0.000906137,685.559 +7,3,0.283333,0.000515114,935.388 +7,3,0.3,0.000399607,578.726 +7,3,0.316667,0.000410291,246.642 +7,3,0.333333,0.000649618,635.453 +7,3,0.35,0.000589736,339.165 +7,3,0.366667,0.000537152,222.893 +7,3,0.383333,0.000748212,680.89 +7,3,0.4,0.000628809,171.681 +7,3,0.416667,0.000796226,415.876 +7,3,0.433333,0.000595893,683.472 +7,3,0.45,0.00084849,1070.92 +7,3,0.466667,0.00064089,721.112 +7,3,0.483333,0.000694683,971.842 +7,3,0.5,0.000794099,470.327 +7,3,0,0,877.458 +7,3,0.0166667,2.67352e-05,884.275 +7,3,0.0333333,5.60358e-05,941.544 +7,3,0.05,9.44367e-05,864.204 +7,3,0.0666667,0.000100363,621.509 +7,3,0.0833333,0.000138732,777.776 +7,3,0.1,0.000170316,1193.17 +7,3,0.116667,0.000161588,191.925 +7,3,0.133333,0.000370895,1088.31 +7,3,0.15,0.000198621,980.317 +7,3,0.166667,0.000244992,697.586 +7,3,0.183333,0.000311562,899.363 +7,3,0.2,0.000236398,837.181 +7,3,0.216667,0.000376738,734.503 +7,3,0.233333,0.00041043,1018.07 +7,3,0.25,0.00106363,255.63 +7,3,0.266667,0.000505177,341.752 +7,3,0.283333,0.000506205,960.491 +7,3,0.3,0.000492048,902.906 +7,3,0.316667,0.00055644,800.659 +7,3,0.333333,0.000527946,152.797 +7,3,0.35,0.000626826,1281.18 +7,3,0.366667,0.000743807,717.648 +7,3,0.383333,0.000564255,1035.02 +7,3,0.4,0.000602322,549.997 +7,3,0.416667,0.000557364,966.768 +7,3,0.433333,0.000824531,403.926 +7,3,0.45,0.00080536,707.358 +7,3,0.466667,0.000793457,1321.43 +7,3,0.483333,0.000982166,975.095 +7,3,0.5,0.000922139,172.622 +7,3,0,0,838.953 +7,3,0.0166667,2.35417e-05,338.965 +7,3,0.0333333,6.62163e-05,812.455 +7,3,0.05,9.19391e-05,1208.84 +7,3,0.0666667,8.88195e-05,637.33 +7,3,0.0833333,0.000149634,1076.57 +7,3,0.1,0.000260481,495.074 +7,3,0.116667,0.000165096,621.319 +7,3,0.133333,0.000259169,409.36 +7,3,0.15,0.000190806,915.134 +7,3,0.166667,0.000311052,864.04 +7,3,0.183333,0.000513764,834.08 +7,3,0.2,0.000343984,724.152 +7,3,0.216667,0.000452712,599.903 +7,3,0.233333,0.000341342,784.939 +7,3,0.25,0.000552749,1143.04 +7,3,0.266667,0.000381999,791.418 +7,3,0.283333,0.00052244,518.496 +7,3,0.3,0.000533811,758.765 +7,3,0.316667,0.000448834,1212.54 +7,3,0.333333,0.000806139,1054.13 +7,3,0.35,0.000627751,802.61 +7,3,0.366667,0.000583878,777.851 +7,3,0.383333,0.000459654,545.304 +7,3,0.4,0.000727794,572.369 +7,3,0.416667,0.000756415,1134.84 +7,3,0.433333,0.000614468,787.069 +7,3,0.45,0.000583059,979.218 +7,3,0.466667,0.000613622,425.813 +7,3,0.483333,0.000957654,116.39 +7,3,0.5,0.0010952,589.975 +7,3,0,0,652.584 +7,3,0.0166667,2.32159e-05,761.49 +7,3,0.0333333,6.02826e-05,887.804 +7,3,0.05,8.97744e-05,599.103 +7,3,0.0666667,0.000125387,512.15 +7,3,0.0833333,0.000144663,826.1 +7,3,0.1,0.000221431,854.491 +7,3,0.116667,0.000214152,1079.98 +7,3,0.133333,0.000203403,1257.68 +7,3,0.15,0.000233039,750.854 +7,3,0.166667,0.000262898,666.279 +7,3,0.183333,0.000293488,1251.98 +7,3,0.2,0.000368106,986.735 +7,3,0.216667,0.000352497,898.93 +7,3,0.233333,0.000369427,852.398 +7,3,0.25,0.000472399,602.202 +7,3,0.266667,0.00046149,1019.04 +7,3,0.283333,0.000588476,1016.01 +7,3,0.3,0.000570345,856.833 +7,3,0.316667,0.00064145,440.708 +7,3,0.333333,0.000637075,665.183 +7,3,0.35,0.000526925,554.138 +7,3,0.366667,0.000680212,1216.09 +7,3,0.383333,0.000885503,136.02 +7,3,0.4,0.000968844,1280.99 +7,3,0.416667,0.000587068,876.809 +7,3,0.433333,0.000791194,894.352 +7,3,0.45,0.000805119,744.734 +7,3,0.466667,0.00075399,239.889 +7,3,0.483333,0.000860401,566.506 +7,3,0.5,0.00112835,723.279 +7,3,0,0,913.468 +7,3,0.0166667,2.52176e-05,217.11 +7,3,0.0333333,5.58676e-05,1243.8 +7,3,0.05,8.83697e-05,409.954 +7,3,0.0666667,9.98891e-05,977.217 +7,3,0.0833333,0.000189033,1241.29 +7,3,0.1,0.000168619,327.399 +7,3,0.116667,0.000231852,1160.01 +7,3,0.133333,0.000253584,745.577 +7,3,0.15,0.000353828,666.854 +7,3,0.166667,0.000281507,810.019 +7,3,0.183333,0.000211997,658.172 +7,3,0.2,0.000427183,555.711 +7,3,0.216667,0.000439489,950.225 +7,3,0.233333,0.000343827,488.613 +7,3,0.25,0.000308568,788.493 +7,3,0.266667,0.000420568,1071.44 +7,3,0.283333,0.000499752,703.032 +7,3,0.3,0.000622839,309.04 +7,3,0.316667,0.000546196,444.121 +7,3,0.333333,0.000600223,958.855 +7,3,0.35,0.000487157,470.549 +7,3,0.366667,0.000576111,1205.66 +7,3,0.383333,0.000742899,313.798 +7,3,0.4,0.000979088,896.306 +7,3,0.416667,0.000799436,780.951 +7,3,0.433333,0.000779749,514.458 +7,3,0.45,0.000797755,1250.52 +7,3,0.466667,0.000860686,1392.69 +7,3,0.483333,0.000777114,1286.06 +7,3,0.5,0.000878068,892.351 +7,3,0,0,439.508 +7,3,0.0166667,3.20145e-05,642.988 +7,3,0.0333333,4.21375e-05,307.021 +7,3,0.05,9.98563e-05,682.524 +7,3,0.0666667,0.000134825,1209.14 +7,3,0.0833333,0.000122358,1017.36 +7,3,0.1,0.000119739,944.058 +7,3,0.116667,0.000187541,977.707 +7,3,0.133333,0.000162059,1036.93 +7,3,0.15,0.000310404,1282.46 +7,3,0.166667,0.000190039,215.075 +7,3,0.183333,0.000272779,686.355 +7,3,0.2,0.000300874,989.425 +7,3,0.216667,0.000375337,741.723 +7,3,0.233333,0.00057442,564.318 +7,3,0.25,0.000286906,632.664 +7,3,0.266667,0.000487811,618.767 +7,3,0.283333,0.000517111,877.901 +7,3,0.3,0.000584981,970.951 +7,3,0.316667,0.000556618,472.441 +7,3,0.333333,0.000785386,724.065 +7,3,0.35,0.000593537,552.874 +7,3,0.366667,0.000518535,1289.99 +7,3,0.383333,0.000540917,721.51 +7,3,0.4,0.000819301,954.191 +7,3,0.416667,0.000863523,864.999 +7,3,0.433333,0.000762987,894.762 +7,3,0.45,0.00081053,35.7767 +7,3,0.466667,0.000729006,1065.3 +7,3,0.483333,0.00080286,824.927 +7,3,0.5,0.00110092,602.41 +7,3,0,0,255.226 +7,3,0.0166667,3.09753e-05,868.318 +7,3,0.0333333,5.69464e-05,500.639 +7,3,0.05,6.39704e-05,365.98 +7,3,0.0666667,0.000152966,922.303 +7,3,0.0833333,0.000135736,851.328 +7,3,0.1,0.000182827,763.489 +7,3,0.116667,0.000198368,661.669 +7,3,0.133333,0.000280802,1001.96 +7,3,0.15,0.000329196,1199.65 +7,3,0.166667,0.000392512,968.71 +7,3,0.183333,0.000297519,1023.26 +7,3,0.2,0.000322876,319.642 +7,3,0.216667,0.000396571,781.711 +7,3,0.233333,0.000424119,306.917 +7,3,0.25,0.000349407,844.016 +7,3,0.266667,0.0002937,683.816 +7,3,0.283333,0.000435607,723.419 +7,3,0.3,0.000600615,665.984 +7,3,0.316667,0.000502338,666.874 +7,3,0.333333,0.000533166,1352.92 +7,3,0.35,0.000461092,483.721 +7,3,0.366667,0.000462009,960.348 +7,3,0.383333,0.000494326,673.702 +7,3,0.4,0.000760611,1088.67 +7,3,0.416667,0.000609796,973.104 +7,3,0.433333,0.000827472,1079.25 +7,3,0.45,0.000854512,718.435 +7,3,0.466667,0.000781764,824.799 +7,3,0.483333,0.000749569,990.825 +7,3,0.5,0.00101182,343.904 +7,3,0,0,641.452 +7,3,0.0166667,4.0044e-05,799.252 +7,3,0.0333333,4.25706e-05,982.157 +7,3,0.05,9.30466e-05,132.641 +7,3,0.0666667,0.00011576,1056.3 +7,3,0.0833333,0.000156601,638.7 +7,3,0.1,0.000176105,157.277 +7,3,0.116667,0.000174165,673.602 +7,3,0.133333,0.000227056,746.563 +7,3,0.15,0.000274028,873.274 +7,3,0.166667,0.000301677,1080.23 +7,3,0.183333,0.000289721,815.539 +7,3,0.2,0.000370695,1088.33 +7,3,0.216667,0.00027351,1331.97 +7,3,0.233333,0.000409803,560.319 +7,3,0.25,0.00050159,834.557 +7,3,0.266667,0.000333663,255.875 +7,3,0.283333,0.000370827,577.079 +7,3,0.3,0.000571745,997.939 +7,3,0.316667,0.000469865,958.512 +7,3,0.333333,0.000497524,1025.88 +7,3,0.35,0.000530102,740.312 +7,3,0.366667,0.000626381,505.723 +7,3,0.383333,0.000514695,377.434 +7,3,0.4,0.000674046,961.922 +7,3,0.416667,0.000613285,605.398 +7,3,0.433333,0.00114596,927.992 +7,3,0.45,0.000905262,541.612 +7,3,0.466667,0.000949471,787.106 +7,3,0.483333,0.000823794,500.666 +7,3,0.5,0.00102638,1331.22 +7,3,0,0,768 +7,3,0.0166667,3.36539e-05,1138.81 +7,3,0.0333333,5.26085e-05,1038.27 +7,3,0.05,8.0564e-05,817.386 +7,3,0.0666667,0.000125774,1103.86 +7,3,0.0833333,0.000112215,88.687 +7,3,0.1,0.000172536,1084.59 +7,3,0.116667,0.000219447,1106.6 +7,3,0.133333,0.000199275,981.689 +7,3,0.15,0.000200936,1092.58 +7,3,0.166667,0.000287654,987.863 +7,3,0.183333,0.00031887,749.701 +7,3,0.2,0.000306373,856.322 +7,3,0.216667,0.000571445,933.187 +7,3,0.233333,0.000425974,514.379 +7,3,0.25,0.000418071,713.146 +7,3,0.266667,0.000374133,1025.93 +7,3,0.283333,0.000484077,1177.96 +7,3,0.3,0.000458702,840.253 +7,3,0.316667,0.000578197,377.375 +7,3,0.333333,0.000645748,808.957 +7,3,0.35,0.000542526,200.089 +7,3,0.366667,0.000651809,596.032 +7,3,0.383333,0.000823613,1210.75 +7,3,0.4,0.000564088,139.986 +7,3,0.416667,0.000559839,209.286 +7,3,0.433333,0.000573288,293.59 +7,3,0.45,0.00071453,755.436 +7,3,0.466667,0.000841938,982.659 +7,3,0.483333,0.000588712,477.751 +7,3,0.5,0.000751135,544.098 +7,3,0,0,638.409 +7,3,0.0166667,4.05829e-05,906.588 +7,3,0.0333333,8.76937e-05,659.048 +7,3,0.05,9.25511e-05,850.604 +7,3,0.0666667,0.00018491,1113.67 +7,3,0.0833333,0.000108212,657.839 +7,3,0.1,0.000203073,913.3 +7,3,0.116667,0.00015254,695.904 +7,3,0.133333,0.000257335,1072.52 +7,3,0.15,0.000243849,772.785 +7,3,0.166667,0.000251172,574.396 +7,3,0.183333,0.000227703,725.513 +7,3,0.2,0.000340853,662.344 +7,3,0.216667,0.000267531,919.105 +7,3,0.233333,0.000387587,951.457 +7,3,0.25,0.000399376,959.902 +7,3,0.266667,0.000320603,217.557 +7,3,0.283333,0.000510847,514.854 +7,3,0.3,0.000557752,1167.72 +7,3,0.316667,0.000762508,1220.26 +7,3,0.333333,0.000470913,568.277 +7,3,0.35,0.000623614,1305.13 +7,3,0.366667,0.000793305,895.792 +7,3,0.383333,0.000488143,492.98 +7,3,0.4,0.000956072,350.008 +7,3,0.416667,0.000660574,799.067 +7,3,0.433333,0.000932813,418.533 +7,3,0.45,0.000843571,804.602 +7,3,0.466667,0.000684229,190.215 +7,3,0.483333,0.000842613,555.652 +7,3,0.5,0.000783768,335.152 +10,4,0,0,670.304 +10,4,0.0166667,3.56912e-05,932.986 +10,4,0.0333333,5.90758e-05,1302.28 +10,4,0.05,0.000115097,1179.36 +10,4,0.0666667,0.000127519,1286.18 +10,4,0.0833333,0.0001783,628.664 +10,4,0.1,0.000148184,1157.6 +10,4,0.116667,0.000213696,951.648 +10,4,0.133333,0.000332392,1342.15 +10,4,0.15,0.000254055,840.128 +10,4,0.166667,0.000298037,558.855 +10,4,0.183333,0.000335433,607.536 +10,4,0.2,0.000292681,1111.81 +10,4,0.216667,0.000440828,1419.11 +10,4,0.233333,0.000317949,1054.69 +10,4,0.25,0.000411726,982.785 +10,4,0.266667,0.000815904,862.534 +10,4,0.283333,0.000570702,681.345 +10,4,0.3,0.000539129,993.713 +10,4,0.316667,0.000672583,746.341 +10,4,0.333333,0.000538467,518.513 +10,4,0.35,0.000588184,1078.89 +10,4,0.366667,0.000836085,652.586 +10,4,0.383333,0.000800995,855.048 +10,4,0.4,0.000871497,1327.55 +10,4,0.416667,0.000819719,1313.06 +10,4,0.433333,0.00139151,1375.93 +10,4,0.45,0.00103342,1310.7 +10,4,0.466667,0.000793098,941.559 +10,4,0.483333,0.000837774,836.344 +10,4,0.5,0.00105041,1257.74 +10,4,0,0,650.574 +10,4,0.0166667,2.91043e-05,406.59 +10,4,0.0333333,6.88567e-05,897.464 +10,4,0.05,9.94492e-05,1196.66 +10,4,0.0666667,0.000114179,1269.15 +10,4,0.0833333,0.000149002,922.027 +10,4,0.1,0.000165069,1241.39 +10,4,0.116667,0.000162739,590.16 +10,4,0.133333,0.000322398,771.341 +10,4,0.15,0.000303623,812.263 +10,4,0.166667,0.0003489,804.93 +10,4,0.183333,0.000329866,928.419 +10,4,0.2,0.000313682,794.633 +10,4,0.216667,0.000343915,829.845 +10,4,0.233333,0.000486575,1211.07 +10,4,0.25,0.000364954,976.688 +10,4,0.266667,0.000449894,1299.83 +10,4,0.283333,0.000568233,1322.04 +10,4,0.3,0.000565379,986.646 +10,4,0.316667,0.000445109,812.489 +10,4,0.333333,0.00052298,695.921 +10,4,0.35,0.000677938,1190.6 +10,4,0.366667,0.000570893,539.625 +10,4,0.383333,0.000750118,1107.87 +10,4,0.4,0.000638561,1060.23 +10,4,0.416667,0.000731741,1061.78 +10,4,0.433333,0.000762459,780.804 +10,4,0.45,0.00118716,1368.36 +10,4,0.466667,0.00110316,948.984 +10,4,0.483333,0.00101855,1292.9 +10,4,0.5,0.000670573,1150.61 +10,4,0,0,949.507 +10,4,0.0166667,2.89266e-05,1343.18 +10,4,0.0333333,4.48046e-05,616.285 +10,4,0.05,0.0001136,1278.97 +10,4,0.0666667,9.34627e-05,894.218 +10,4,0.0833333,0.000128139,1048 +10,4,0.1,0.000235773,1242.9 +10,4,0.116667,0.000204233,415.582 +10,4,0.133333,0.000297729,1431.9 +10,4,0.15,0.000300906,1092.25 +10,4,0.166667,0.000333,581.22 +10,4,0.183333,0.000301773,1278.99 +10,4,0.2,0.000428126,699.608 +10,4,0.216667,0.000399725,547.718 +10,4,0.233333,0.000705483,1095.96 +10,4,0.25,0.000426134,1151.77 +10,4,0.266667,0.000452496,826.059 +10,4,0.283333,0.000450184,680.234 +10,4,0.3,0.000695556,1279.52 +10,4,0.316667,0.00044924,634.508 +10,4,0.333333,0.000828397,1523.01 +10,4,0.35,0.000591075,1256.28 +10,4,0.366667,0.000394863,624.661 +10,4,0.383333,0.000600217,710.845 +10,4,0.4,0.0006871,972.943 +10,4,0.416667,0.00075627,1045.47 +10,4,0.433333,0.000832314,1134.28 +10,4,0.45,0.000762769,1102.79 +10,4,0.466667,0.000975126,549.837 +10,4,0.483333,0.000910378,605.52 +10,4,0.5,0.00100977,1148.25 +10,4,0,0,768.179 +10,4,0.0166667,3.00217e-05,1178.11 +10,4,0.0333333,5.38129e-05,1197.76 +10,4,0.05,8.21442e-05,928.693 +10,4,0.0666667,0.000138642,833.879 +10,4,0.0833333,0.000124054,580.979 +10,4,0.1,0.000136376,425.939 +10,4,0.116667,0.000186023,1070.3 +10,4,0.133333,0.000294228,940.375 +10,4,0.15,0.000292249,772.026 +10,4,0.166667,0.000351915,734.965 +10,4,0.183333,0.000270372,1377.3 +10,4,0.2,0.000404737,1326.96 +10,4,0.216667,0.000371539,1170.56 +10,4,0.233333,0.000408308,737.171 +10,4,0.25,0.000448696,459.303 +10,4,0.266667,0.000431087,505.468 +10,4,0.283333,0.000494558,903.582 +10,4,0.3,0.000464056,550.118 +10,4,0.316667,0.000443679,955.517 +10,4,0.333333,0.000497462,858.863 +10,4,0.35,0.000743725,1324.98 +10,4,0.366667,0.000697112,1242.11 +10,4,0.383333,0.000753774,1393.28 +10,4,0.4,0.000664503,791.509 +10,4,0.416667,0.000704199,720.578 +10,4,0.433333,0.000780079,950.327 +10,4,0.45,0.000600375,972.329 +10,4,0.466667,0.000905401,860.393 +10,4,0.483333,0.000737863,610.673 +10,4,0.5,0.000948925,1344.53 +10,4,0,0,1326.41 +10,4,0.0166667,2.89618e-05,1005.29 +10,4,0.0333333,6.31163e-05,660.398 +10,4,0.05,0.000133388,1240.71 +10,4,0.0666667,0.000124253,1221.38 +10,4,0.0833333,0.000153115,864 +10,4,0.1,0.000248763,1220.28 +10,4,0.116667,0.00023572,707.301 +10,4,0.133333,0.000221897,700.086 +10,4,0.15,0.000232907,1278.32 +10,4,0.166667,0.00030403,1073.36 +10,4,0.183333,0.000341579,1279.77 +10,4,0.2,0.000389702,858.471 +10,4,0.216667,0.000281352,838.489 +10,4,0.233333,0.000424834,907.074 +10,4,0.25,0.00039736,978.165 +10,4,0.266667,0.00043902,842.068 +10,4,0.283333,0.000509345,1160.75 +10,4,0.3,0.000692299,1276.78 +10,4,0.316667,0.000707351,1149.13 +10,4,0.333333,0.000624337,1354.17 +10,4,0.35,0.000689997,659.97 +10,4,0.366667,0.000566494,820.805 +10,4,0.383333,0.000759324,1159.9 +10,4,0.4,0.000663616,914.276 +10,4,0.416667,0.000790772,1106.87 +10,4,0.433333,0.000884569,1277.46 +10,4,0.45,0.000852322,1214.45 +10,4,0.466667,0.000822982,1454.07 +10,4,0.483333,0.000742079,1330.66 +10,4,0.5,0.000816296,993.927 +10,4,0,0,1215.83 +10,4,0.0166667,2.46016e-05,893.553 +10,4,0.0333333,5.19766e-05,1072.7 +10,4,0.05,8.39487e-05,1007.01 +10,4,0.0666667,0.000146199,440.965 +10,4,0.0833333,0.000124461,1163.09 +10,4,0.1,0.000222229,1063.06 +10,4,0.116667,0.000289202,433.138 +10,4,0.133333,0.000250469,1313.16 +10,4,0.15,0.000366188,1206.24 +10,4,0.166667,0.000296728,1273.11 +10,4,0.183333,0.000429562,1081.85 +10,4,0.2,0.000356506,990.941 +10,4,0.216667,0.000389469,1118.28 +10,4,0.233333,0.000411143,471.267 +10,4,0.25,0.00054193,939.649 +10,4,0.266667,0.000402849,949.842 +10,4,0.283333,0.000455142,1044.48 +10,4,0.3,0.000729955,885.872 +10,4,0.316667,0.000530664,1243.27 +10,4,0.333333,0.000434002,948.089 +10,4,0.35,0.000581052,1198.01 +10,4,0.366667,0.000620238,993.234 +10,4,0.383333,0.000783938,1206.59 +10,4,0.4,0.000728247,1342.07 +10,4,0.416667,0.000611297,975.955 +10,4,0.433333,0.000796348,1341.47 +10,4,0.45,0.000630454,1133.97 +10,4,0.466667,0.00095099,1266.55 +10,4,0.483333,0.000847105,747.121 +10,4,0.5,0.00093312,1061.36 +10,4,0,0,539.648 +10,4,0.0166667,3.596e-05,870.597 +10,4,0.0333333,6.13501e-05,1181.56 +10,4,0.05,9.21687e-05,810.62 +10,4,0.0666667,9.27745e-05,798.473 +10,4,0.0833333,0.000135003,844.283 +10,4,0.1,0.000170635,542.886 +10,4,0.116667,0.000256975,637.861 +10,4,0.133333,0.000203533,1096.82 +10,4,0.15,0.000254146,803.71 +10,4,0.166667,0.000284528,1182.86 +10,4,0.183333,0.000357339,1268.11 +10,4,0.2,0.000376942,1208.25 +10,4,0.216667,0.000330934,880.291 +10,4,0.233333,0.000404304,949.048 +10,4,0.25,0.000493543,456.574 +10,4,0.266667,0.000548685,675.912 +10,4,0.283333,0.000459548,1028.04 +10,4,0.3,0.000802075,895.223 +10,4,0.316667,0.000512933,947.134 +10,4,0.333333,0.000698054,1192.43 +10,4,0.35,0.000635922,1006.36 +10,4,0.366667,0.000680041,345.36 +10,4,0.383333,0.000586981,636.235 +10,4,0.4,0.000832622,1098.69 +10,4,0.416667,0.00070151,616.784 +10,4,0.433333,0.00100518,865.82 +10,4,0.45,0.000918388,1275.01 +10,4,0.466667,0.00101429,917.001 +10,4,0.483333,0.000805743,1255.71 +10,4,0.5,0.00116449,1113.67 +10,4,0,0,681.852 +10,4,0.0166667,2.37338e-05,727.959 +10,4,0.0333333,6.35045e-05,654.938 +10,4,0.05,8.90695e-05,1163.83 +10,4,0.0666667,0.000102503,773.491 +10,4,0.0833333,0.000144318,666.313 +10,4,0.1,0.000144458,964.9 +10,4,0.116667,0.000191888,615.945 +10,4,0.133333,0.000200416,719.056 +10,4,0.15,0.000279473,1409.92 +10,4,0.166667,0.000355832,1113.65 +10,4,0.183333,0.000291067,813.609 +10,4,0.2,0.000371793,1139.31 +10,4,0.216667,0.000397743,1220.6 +10,4,0.233333,0.000456158,1179.73 +10,4,0.25,0.000362813,558.705 +10,4,0.266667,0.000491253,626.284 +10,4,0.283333,0.000565029,976.715 +10,4,0.3,0.000492332,953.963 +10,4,0.316667,0.000744907,268.074 +10,4,0.333333,0.000949178,1100.29 +10,4,0.35,0.000602749,847.807 +10,4,0.366667,0.00071266,435.226 +10,4,0.383333,0.000627456,806.268 +10,4,0.4,0.000696629,1240.51 +10,4,0.416667,0.000632977,1019.66 +10,4,0.433333,0.000779817,551.757 +10,4,0.45,0.000809212,1136.3 +10,4,0.466667,0.000883078,1138.25 +10,4,0.483333,0.000606979,612.487 +10,4,0.5,0.000895295,1323.52 +10,4,0,0,1148.22 +10,4,0.0166667,4.88912e-05,1093.37 +10,4,0.0333333,5.27415e-05,1101.34 +10,4,0.05,7.68962e-05,1180.77 +10,4,0.0666667,0.000125004,1196.2 +10,4,0.0833333,0.000182197,1316.35 +10,4,0.1,0.000214627,996.896 +10,4,0.116667,0.00017724,993.835 +10,4,0.133333,0.000306695,1090.89 +10,4,0.15,0.000239616,1086.73 +10,4,0.166667,0.000248085,1070.93 +10,4,0.183333,0.000313852,772.635 +10,4,0.2,0.000383013,746.159 +10,4,0.216667,0.0003622,1005.47 +10,4,0.233333,0.000387953,1173.75 +10,4,0.25,0.000390501,813.699 +10,4,0.266667,0.0004244,608.294 +10,4,0.283333,0.000773909,1240.06 +10,4,0.3,0.000501602,949.687 +10,4,0.316667,0.000518496,668.756 +10,4,0.333333,0.000555372,1521.56 +10,4,0.35,0.000740519,1379 +10,4,0.366667,0.00063497,1293.08 +10,4,0.383333,0.000615638,1597.6 +10,4,0.4,0.000734359,1161.84 +10,4,0.416667,0.000762768,867.512 +10,4,0.433333,0.000737006,1396.59 +10,4,0.45,0.000843266,1149.44 +10,4,0.466667,0.000652939,856.642 +10,4,0.483333,0.000841873,1341.05 +10,4,0.5,0.000969744,1256.94 +10,4,0,0,994.419 +10,4,0.0166667,2.61647e-05,1087.77 +10,4,0.0333333,6.36867e-05,1145.49 +10,4,0.05,7.66788e-05,1149.07 +10,4,0.0666667,0.000105603,1143.2 +10,4,0.0833333,0.000123164,611.745 +10,4,0.1,0.000166472,899.77 +10,4,0.116667,0.000199136,693.121 +10,4,0.133333,0.000202621,1019.21 +10,4,0.15,0.000291099,1407.7 +10,4,0.166667,0.000238928,641.271 +10,4,0.183333,0.00034301,946.717 +10,4,0.2,0.000361812,1298.43 +10,4,0.216667,0.000384188,455.233 +10,4,0.233333,0.000492078,1006.89 +10,4,0.25,0.000417885,699.985 +10,4,0.266667,0.000512512,582.037 +10,4,0.283333,0.000466545,872.267 +10,4,0.3,0.000625656,1040.75 +10,4,0.316667,0.000642005,1343.74 +10,4,0.333333,0.000488795,1131.7 +10,4,0.35,0.000540237,923.871 +10,4,0.366667,0.000553349,583.122 +10,4,0.383333,0.000754769,1091.56 +10,4,0.4,0.00100631,757.448 +10,4,0.416667,0.000838564,1172.24 +10,4,0.433333,0.00076361,1254.19 +10,4,0.45,0.000731391,1156.56 +10,4,0.466667,0.000788853,329.268 +10,4,0.483333,0.000713489,981.764 +10,4,0.5,0.000762387,671.229 +10,5,0,0,535.863 +10,5,0.0166667,3.36297e-05,1019.09 +10,5,0.0333333,5.83329e-05,1086.31 +10,5,0.05,0.000116597,1007.44 +10,5,0.0666667,0.000136636,1302.92 +10,5,0.0833333,0.000147656,1430.55 +10,5,0.1,0.000214735,1169.41 +10,5,0.116667,0.000279735,1243.62 +10,5,0.133333,0.000208752,906.599 +10,5,0.15,0.000535856,1415.9 +10,5,0.166667,0.000406751,975.984 +10,5,0.183333,0.000440091,976.722 +10,5,0.2,0.000359392,1018.86 +10,5,0.216667,0.00041688,1088.34 +10,5,0.233333,0.000413805,1558.56 +10,5,0.25,0.000437722,839.449 +10,5,0.266667,0.000602957,541.806 +10,5,0.283333,0.000488948,954.213 +10,5,0.3,0.000597999,1363.51 +10,5,0.316667,0.00063053,1655.6 +10,5,0.333333,0.000760975,1201.06 +10,5,0.35,0.000570898,1062.11 +10,5,0.366667,0.00101888,1447.98 +10,5,0.383333,0.00071352,973.428 +10,5,0.4,0.000727632,1218.9 +10,5,0.416667,0.000721392,1004.18 +10,5,0.433333,0.00113339,813.499 +10,5,0.45,0.000723362,1243.69 +10,5,0.466667,0.000830295,1138.83 +10,5,0.483333,0.00110659,1418.71 +10,5,0.5,0.00104745,1387.89 +10,5,0,0,1099.79 +10,5,0.0166667,2.53128e-05,1156.24 +10,5,0.0333333,7.00237e-05,1536.75 +10,5,0.05,7.72349e-05,581.784 +10,5,0.0666667,0.000104539,1391.92 +10,5,0.0833333,0.000143295,1353.04 +10,5,0.1,0.000172073,1036.9 +10,5,0.116667,0.000245074,1404.89 +10,5,0.133333,0.000253048,1152.62 +10,5,0.15,0.00022082,824.326 +10,5,0.166667,0.000316485,1255.29 +10,5,0.183333,0.00030662,1186.04 +10,5,0.2,0.000527401,1407.12 +10,5,0.216667,0.00041632,1533.38 +10,5,0.233333,0.000410714,862.183 +10,5,0.25,0.000439799,1147.85 +10,5,0.266667,0.000381218,630.219 +10,5,0.283333,0.000524719,1272.39 +10,5,0.3,0.000544953,860.263 +10,5,0.316667,0.000840617,1389.35 +10,5,0.333333,0.000572148,951.294 +10,5,0.35,0.00108102,1474.5 +10,5,0.366667,0.000879235,590.499 +10,5,0.383333,0.000617057,1036.69 +10,5,0.4,0.000728193,1221.18 +10,5,0.416667,0.00091844,1130.71 +10,5,0.433333,0.000839476,1619.55 +10,5,0.45,0.000898125,675.187 +10,5,0.466667,0.0011851,725.293 +10,5,0.483333,0.000836676,569.315 +10,5,0.5,0.00147942,767.377 +10,5,0,0,1079.74 +10,5,0.0166667,3.30144e-05,1595.11 +10,5,0.0333333,5.23999e-05,611.827 +10,5,0.05,0.00010835,1194.51 +10,5,0.0666667,0.000166062,975.307 +10,5,0.0833333,0.000151418,605.633 +10,5,0.1,0.000218014,1344.57 +10,5,0.116667,0.000214579,740.912 +10,5,0.133333,0.000239455,1201.99 +10,5,0.15,0.00030354,976.611 +10,5,0.166667,0.000265966,1052.12 +10,5,0.183333,0.000290931,1058.25 +10,5,0.2,0.000291402,816.035 +10,5,0.216667,0.000390057,1643.49 +10,5,0.233333,0.000426542,1154.84 +10,5,0.25,0.000519588,1153.93 +10,5,0.266667,0.000524908,589.016 +10,5,0.283333,0.00071902,1168.42 +10,5,0.3,0.000475506,1535.14 +10,5,0.316667,0.000700694,440.819 +10,5,0.333333,0.000804667,984.161 +10,5,0.35,0.00065235,977.916 +10,5,0.366667,0.000752991,1013.43 +10,5,0.383333,0.000555305,727.908 +10,5,0.4,0.000787992,1165.47 +10,5,0.416667,0.000770424,762.339 +10,5,0.433333,0.000669454,1017.86 +10,5,0.45,0.00159744,1398.65 +10,5,0.466667,0.000851879,802.703 +10,5,0.483333,0.000760785,1175.18 +10,5,0.5,0.000862483,1317.81 +10,5,0,0,749.35 +10,5,0.0166667,4.51133e-05,1394.31 +10,5,0.0333333,4.55907e-05,886.866 +10,5,0.05,0.000122955,875.086 +10,5,0.0666667,0.000174761,1334.47 +10,5,0.0833333,0.000187519,1019.65 +10,5,0.1,0.000185908,1272.87 +10,5,0.116667,0.000220477,1073.55 +10,5,0.133333,0.00027677,1116.83 +10,5,0.15,0.000447024,648.872 +10,5,0.166667,0.000360776,768.301 +10,5,0.183333,0.000340869,449.694 +10,5,0.2,0.00047548,1232.24 +10,5,0.216667,0.00036232,1207.55 +10,5,0.233333,0.000528383,1613.16 +10,5,0.25,0.000501525,1387.21 +10,5,0.266667,0.000453121,1204.12 +10,5,0.283333,0.000423643,1028.58 +10,5,0.3,0.000688479,810.293 +10,5,0.316667,0.000476035,1098.55 +10,5,0.333333,0.00063237,798.471 +10,5,0.35,0.000589508,539.196 +10,5,0.366667,0.000814582,1068.53 +10,5,0.383333,0.000965832,976.298 +10,5,0.4,0.000792745,1450.84 +10,5,0.416667,0.000671452,1006.36 +10,5,0.433333,0.000948089,1241.04 +10,5,0.45,0.000766643,1092.38 +10,5,0.466667,0.000814446,1025.43 +10,5,0.483333,0.00111179,678.607 +10,5,0.5,0.000990773,1296.21 +10,5,0,0,1277.55 +10,5,0.0166667,4.76399e-05,1263.69 +10,5,0.0333333,7.59185e-05,724.807 +10,5,0.05,8.36178e-05,890.036 +10,5,0.0666667,0.000134758,1389.5 +10,5,0.0833333,0.000175141,1077.31 +10,5,0.1,0.000199501,608.572 +10,5,0.116667,0.00017718,1264.44 +10,5,0.133333,0.000196074,1206.87 +10,5,0.15,0.000251895,800.88 +10,5,0.166667,0.00023382,667.195 +10,5,0.183333,0.000470209,749.245 +10,5,0.2,0.000343709,1202.07 +10,5,0.216667,0.000381998,1112.25 +10,5,0.233333,0.000592045,1632.46 +10,5,0.25,0.000458468,947.42 +10,5,0.266667,0.000458447,1077.96 +10,5,0.283333,0.000610476,1224.96 +10,5,0.3,0.00050828,1297.92 +10,5,0.316667,0.000705665,1154.7 +10,5,0.333333,0.000784103,1550.19 +10,5,0.35,0.000708294,1250.02 +10,5,0.366667,0.000876889,345.634 +10,5,0.383333,0.000681118,1081.03 +10,5,0.4,0.000691757,911.067 +10,5,0.416667,0.00107271,1112.36 +10,5,0.433333,0.000651931,1239.75 +10,5,0.45,0.00125958,824.181 +10,5,0.466667,0.00113037,1328.13 +10,5,0.483333,0.00107842,1143.11 +10,5,0.5,0.000891908,1158.64 +10,5,0,0,1075.3 +10,5,0.0166667,3.84057e-05,1664.09 +10,5,0.0333333,0.000133588,1156.83 +10,5,0.05,0.000107048,1421.87 +10,5,0.0666667,0.000132254,1420.63 +10,5,0.0833333,0.00014739,1276.35 +10,5,0.1,0.000164075,1093.59 +10,5,0.116667,0.000235309,1206.35 +10,5,0.133333,0.000328404,1379.58 +10,5,0.15,0.000251786,802.316 +10,5,0.166667,0.000288557,808.483 +10,5,0.183333,0.000298392,943.199 +10,5,0.2,0.000509639,1317.18 +10,5,0.216667,0.000405534,978.881 +10,5,0.233333,0.000463005,1398.11 +10,5,0.25,0.000439287,1462.71 +10,5,0.266667,0.000511922,1412.81 +10,5,0.283333,0.00069691,767.441 +10,5,0.3,0.000441318,851.179 +10,5,0.316667,0.000604448,1049.48 +10,5,0.333333,0.000669995,1619.79 +10,5,0.35,0.000551569,1248.09 +10,5,0.366667,0.000677575,1492.3 +10,5,0.383333,0.000751828,999.895 +10,5,0.4,0.000828583,1387.76 +10,5,0.416667,0.000813687,1611.76 +10,5,0.433333,0.00102855,1145.3 +10,5,0.45,0.000694022,522.665 +10,5,0.466667,0.00106853,1381.05 +10,5,0.483333,0.000992426,1071.46 +10,5,0.5,0.0010959,970.66 +10,5,0,0,1642.44 +10,5,0.0166667,2.8387e-05,1178.42 +10,5,0.0333333,6.71022e-05,1476.36 +10,5,0.05,9.48777e-05,994.458 +10,5,0.0666667,0.000142858,1142.48 +10,5,0.0833333,0.000192201,979.797 +10,5,0.1,0.000200181,1142.17 +10,5,0.116667,0.000242296,1128.49 +10,5,0.133333,0.000232504,429.955 +10,5,0.15,0.000265443,744.048 +10,5,0.166667,0.000337079,640.788 +10,5,0.183333,0.000521987,1304.85 +10,5,0.2,0.000376884,1226.79 +10,5,0.216667,0.00038395,1250.59 +10,5,0.233333,0.000475341,1113.56 +10,5,0.25,0.000601587,791.371 +10,5,0.266667,0.000794626,1152.46 +10,5,0.283333,0.000712983,1060.38 +10,5,0.3,0.00068173,815.189 +10,5,0.316667,0.000773917,884.209 +10,5,0.333333,0.000652898,1483.01 +10,5,0.35,0.000656267,1377.41 +10,5,0.366667,0.000700673,1300.78 +10,5,0.383333,0.000671263,1091.31 +10,5,0.4,0.00127446,1339.43 +10,5,0.416667,0.000836924,1176.63 +10,5,0.433333,0.000716381,1238.28 +10,5,0.45,0.00099901,1422.53 +10,5,0.466667,0.000999116,1118.93 +10,5,0.483333,0.000868162,703.241 +10,5,0.5,0.00104439,1263.18 +10,5,0,0,792.569 +10,5,0.0166667,3.38778e-05,1098.54 +10,5,0.0333333,6.81853e-05,1323.7 +10,5,0.05,0.00010137,1061.71 +10,5,0.0666667,0.000171389,1250.74 +10,5,0.0833333,0.000139541,828.512 +10,5,0.1,0.000198011,968.368 +10,5,0.116667,0.000194767,1203.62 +10,5,0.133333,0.000259345,1399.88 +10,5,0.15,0.00027087,1228.38 +10,5,0.166667,0.000347393,1516.22 +10,5,0.183333,0.000335794,1506.11 +10,5,0.2,0.000307158,886.642 +10,5,0.216667,0.000502494,856.662 +10,5,0.233333,0.000505054,512.551 +10,5,0.25,0.000630935,662.995 +10,5,0.266667,0.000643606,1246.81 +10,5,0.283333,0.000500917,1616.74 +10,5,0.3,0.000676047,1140.57 +10,5,0.316667,0.000418904,985.733 +10,5,0.333333,0.000521342,935.788 +10,5,0.35,0.000647404,1301.07 +10,5,0.366667,0.000614079,1120.94 +10,5,0.383333,0.00084623,1377.07 +10,5,0.4,0.000865219,1405.16 +10,5,0.416667,0.000663605,1212.27 +10,5,0.433333,0.000750731,1249.52 +10,5,0.45,0.000792937,1088.85 +10,5,0.466667,0.00102463,1593.44 +10,5,0.483333,0.000774571,1165.36 +10,5,0.5,0.00106653,952.838 +10,5,0,0,1078.29 +10,5,0.0166667,3.21204e-05,1472.4 +10,5,0.0333333,6.00073e-05,1414.09 +10,5,0.05,8.58838e-05,845.475 +10,5,0.0666667,0.000113714,886.507 +10,5,0.0833333,0.000185117,1280.69 +10,5,0.1,0.000161943,834.943 +10,5,0.116667,0.000215645,1374.01 +10,5,0.133333,0.000239136,1289.45 +10,5,0.15,0.00030694,1267.19 +10,5,0.166667,0.000412262,1376.79 +10,5,0.183333,0.000396954,1439.7 +10,5,0.2,0.000438238,1636.85 +10,5,0.216667,0.000422643,818.282 +10,5,0.233333,0.000378296,677.103 +10,5,0.25,0.000667716,1475.81 +10,5,0.266667,0.000541083,1328.81 +10,5,0.283333,0.000483295,1032.51 +10,5,0.3,0.000574445,1383.4 +10,5,0.316667,0.000944115,1016.99 +10,5,0.333333,0.000837973,1115.01 +10,5,0.35,0.000740385,1558.83 +10,5,0.366667,0.000634933,1097.94 +10,5,0.383333,0.000994038,917.011 +10,5,0.4,0.000708311,1303.08 +10,5,0.416667,0.000588556,861.456 +10,5,0.433333,0.00107211,1283.48 +10,5,0.45,0.000819851,1098.27 +10,5,0.466667,0.0019823,1068.53 +10,5,0.483333,0.00118158,1275.91 +10,5,0.5,0.000960759,1581.99 +10,5,0,0,1341.89 +10,5,0.0166667,3.03771e-05,1179.71 +10,5,0.0333333,7.30219e-05,974.687 +10,5,0.05,8.45036e-05,1511.57 +10,5,0.0666667,0.000134079,1304.44 +10,5,0.0833333,0.000139326,993.128 +10,5,0.1,0.000218586,881.235 +10,5,0.116667,0.000246393,1252.78 +10,5,0.133333,0.000273081,1175.9 +10,5,0.15,0.000299736,1323.21 +10,5,0.166667,0.000405038,1256.57 +10,5,0.183333,0.000333461,1386.61 +10,5,0.2,0.000296443,1421.89 +10,5,0.216667,0.000418195,1131.94 +10,5,0.233333,0.000483587,950.234 +10,5,0.25,0.000445175,1152.15 +10,5,0.266667,0.000582466,1314.68 +10,5,0.283333,0.000671814,1259.99 +10,5,0.3,0.000659697,912.131 +10,5,0.316667,0.00065783,1820.29 +10,5,0.333333,0.000844833,1656.11 +10,5,0.35,0.000607421,1229.97 +10,5,0.366667,0.000669052,1159.06 +10,5,0.383333,0.000925863,1254.74 +10,5,0.4,0.000727275,1380.73 +10,5,0.416667,0.0011725,1328.94 +10,5,0.433333,0.000877765,1158.32 +10,5,0.45,0.00095777,1109.69 +10,5,0.466667,0.000726985,702.217 +10,5,0.483333,0.00107426,1406.23 +10,5,0.5,0.00120551,1379.62 +11,6,0,0,981.124 +11,6,0.0166667,3.6683e-05,1274.11 +11,6,0.0333333,6.21851e-05,1149.1 +11,6,0.05,0.00010657,1142.92 +11,6,0.0666667,0.0001533,1458.57 +11,6,0.0833333,0.00027136,1156.77 +11,6,0.1,0.000206622,1280.07 +11,6,0.116667,0.000273655,1432.85 +11,6,0.133333,0.000242889,1339.49 +11,6,0.15,0.000237849,1505.99 +11,6,0.166667,0.000449887,810.376 +11,6,0.183333,0.000413064,1688.43 +11,6,0.2,0.000516151,1321.54 +11,6,0.216667,0.000445545,1103.66 +11,6,0.233333,0.000578412,1526.93 +11,6,0.25,0.000528209,1566.33 +11,6,0.266667,0.000513005,644.58 +11,6,0.283333,0.000904818,671.208 +11,6,0.3,0.000547118,1680.09 +11,6,0.316667,0.000726512,1165.14 +11,6,0.333333,0.00176642,1467.6 +11,6,0.35,0.000601445,929.408 +11,6,0.366667,0.00100298,1384.07 +11,6,0.383333,0.00103505,1573.37 +11,6,0.4,0.000682297,1284.24 +11,6,0.416667,0.00072505,1353.15 +11,6,0.433333,0.00114909,1157.32 +11,6,0.45,0.000872395,1401.49 +11,6,0.466667,0.000836066,880.955 +11,6,0.483333,0.000984237,1368.11 +11,6,0.5,0.000984527,1153.75 +11,6,0,0,1574.62 +11,6,0.0166667,3.29304e-05,1298.1 +11,6,0.0333333,5.69525e-05,1524.25 +11,6,0.05,0.000105692,1266.76 +11,6,0.0666667,0.00015153,745.923 +11,6,0.0833333,0.000335659,1563.37 +11,6,0.1,0.000227111,1383.71 +11,6,0.116667,0.000287855,1280.27 +11,6,0.133333,0.000415108,1376.8 +11,6,0.15,0.000280358,1531.05 +11,6,0.166667,0.000348248,1582.8 +11,6,0.183333,0.000385877,1175.82 +11,6,0.2,0.00035604,969.917 +11,6,0.216667,0.000593172,1396.14 +11,6,0.233333,0.000374381,777.571 +11,6,0.25,0.000460407,1386.77 +11,6,0.266667,0.000394899,1164.19 +11,6,0.283333,0.000674105,1554.43 +11,6,0.3,0.000522131,1730.02 +11,6,0.316667,0.000631959,1729.37 +11,6,0.333333,0.000585259,1209.34 +11,6,0.35,0.000565743,1075.78 +11,6,0.366667,0.000801549,1278.47 +11,6,0.383333,0.000620292,950.163 +11,6,0.4,0.000714709,1461.8 +11,6,0.416667,0.000701882,1515.28 +11,6,0.433333,0.000870361,1587.19 +11,6,0.45,0.000961956,1421.44 +11,6,0.466667,0.000763437,1201.04 +11,6,0.483333,0.000927579,1377.48 +11,6,0.5,0.00102074,1561.93 +11,6,0,0,1585.91 +11,6,0.0166667,4.33352e-05,1608.69 +11,6,0.0333333,7.85454e-05,1455.03 +11,6,0.05,0.000106252,1556.33 +11,6,0.0666667,0.000203163,1406.07 +11,6,0.0833333,0.000307387,1129.63 +11,6,0.1,0.000145002,803.406 +11,6,0.116667,0.000263188,1707.98 +11,6,0.133333,0.000338946,1166.69 +11,6,0.15,0.000374912,1202.57 +11,6,0.166667,0.000340449,1491.2 +11,6,0.183333,0.000484412,1665.29 +11,6,0.2,0.000411425,1317.15 +11,6,0.216667,0.000383786,1074.01 +11,6,0.233333,0.000793533,1434.88 +11,6,0.25,0.000622639,1232.21 +11,6,0.266667,0.000539574,1336.93 +11,6,0.283333,0.000423209,1246.01 +11,6,0.3,0.000881005,1528.89 +11,6,0.316667,0.00100617,1463.48 +11,6,0.333333,0.00120413,1591.44 +11,6,0.35,0.000704201,1473.65 +11,6,0.366667,0.000734951,1130.88 +11,6,0.383333,0.000655133,1100.67 +11,6,0.4,0.00096315,1228.66 +11,6,0.416667,0.00120887,1246.78 +11,6,0.433333,0.000991124,1697.39 +11,6,0.45,0.000829489,1010.7 +11,6,0.466667,0.00102348,1725.47 +11,6,0.483333,0.000932244,1482.05 +11,6,0.5,0.000747936,1257.45 +11,6,0,0,1352.42 +11,6,0.0166667,2.92932e-05,1298.58 +11,6,0.0333333,7.61534e-05,1053.66 +11,6,0.05,0.000123356,1729.22 +11,6,0.0666667,0.000111241,808.176 +11,6,0.0833333,0.000184144,1177.57 +11,6,0.1,0.000177287,645.95 +11,6,0.116667,0.000325215,1556.19 +11,6,0.133333,0.000236289,1517.6 +11,6,0.15,0.00033798,982.083 +11,6,0.166667,0.000368505,1165.38 +11,6,0.183333,0.00036861,1108.59 +11,6,0.2,0.000550573,1113.72 +11,6,0.216667,0.00046351,757.673 +11,6,0.233333,0.000449736,1202.07 +11,6,0.25,0.000758288,1719.41 +11,6,0.266667,0.000475724,1089.12 +11,6,0.283333,0.00101314,1112.77 +11,6,0.3,0.000570992,883.281 +11,6,0.316667,0.000970108,1388.98 +11,6,0.333333,0.000928209,1006.83 +11,6,0.35,0.000614022,1104.26 +11,6,0.366667,0.000788902,1622.43 +11,6,0.383333,0.00216973,1800.39 +11,6,0.4,0.000685622,1361.69 +11,6,0.416667,0.000832772,1218.16 +11,6,0.433333,0.000906662,1425.13 +11,6,0.45,0.000779457,1640.98 +11,6,0.466667,0.00111182,1360.78 +11,6,0.483333,0.000733887,1308.01 +11,6,0.5,0.0011895,1255.16 +11,6,0,0,1749.6 +11,6,0.0166667,3.77966e-05,1013.22 +11,6,0.0333333,8.13615e-05,1617.46 +11,6,0.05,9.98118e-05,1570.09 +11,6,0.0666667,0.000103516,1498.15 +11,6,0.0833333,0.000186444,1427.89 +11,6,0.1,0.000206166,1377.73 +11,6,0.116667,0.000380736,1631.55 +11,6,0.133333,0.000469495,1366.1 +11,6,0.15,0.000295118,1275.03 +11,6,0.166667,0.000256039,628.717 +11,6,0.183333,0.000336318,1154.88 +11,6,0.2,0.000447585,1624.87 +11,6,0.216667,0.00042326,1312.29 +11,6,0.233333,0.000735068,1850.83 +11,6,0.25,0.000660923,1359.46 +11,6,0.266667,0.000512667,953.78 +11,6,0.283333,0.000934183,1417.57 +11,6,0.3,0.000548787,1516.01 +11,6,0.316667,0.000597897,915.438 +11,6,0.333333,0.000840832,855.95 +11,6,0.35,0.000888093,1577.45 +11,6,0.366667,0.00120355,1112.39 +11,6,0.383333,0.000789832,1148.79 +11,6,0.4,0.000881784,1639.88 +11,6,0.416667,0.00071922,1418.03 +11,6,0.433333,0.000816568,953.936 +11,6,0.45,0.000693412,1056.29 +11,6,0.466667,0.00133165,941.008 +11,6,0.483333,0.00180356,1517.7 +11,6,0.5,0.00115857,1603.62 +11,6,0,0,981.588 +11,6,0.0166667,3.67749e-05,1274.17 +11,6,0.0333333,8.28779e-05,928.988 +11,6,0.05,0.000199369,1767.42 +11,6,0.0666667,0.000132432,1456.76 +11,6,0.0833333,0.000291103,1522.83 +11,6,0.1,0.000240202,968.27 +11,6,0.116667,0.000639478,1352.7 +11,6,0.133333,0.000249377,1211.42 +11,6,0.15,0.000250023,1257.95 +11,6,0.166667,0.00032745,1160.13 +11,6,0.183333,0.000424145,998.992 +11,6,0.2,0.000458755,1441.21 +11,6,0.216667,0.000328657,1401.74 +11,6,0.233333,0.000475559,1139.75 +11,6,0.25,0.000583103,1565.95 +11,6,0.266667,0.000475327,1834.06 +11,6,0.283333,0.000540104,1351.08 +11,6,0.3,0.000569914,1138.63 +11,6,0.316667,0.000990148,1151.49 +11,6,0.333333,0.000626219,1688.83 +11,6,0.35,0.000883584,1039.76 +11,6,0.366667,0.000643883,1339.82 +11,6,0.383333,0.000744657,1429.77 +11,6,0.4,0.000844059,1588.57 +11,6,0.416667,0.00099362,1485.22 +11,6,0.433333,0.0010248,1118.02 +11,6,0.45,0.000854908,1136.05 +11,6,0.466667,0.000813837,1272.22 +11,6,0.483333,0.000814945,1059.32 +11,6,0.5,0.000811185,783.684 +11,6,0,0,1292.97 +11,6,0.0166667,3.03916e-05,1511.78 +11,6,0.0333333,7.11164e-05,1273.55 +11,6,0.05,9.51127e-05,867.514 +11,6,0.0666667,0.000176249,688.277 +11,6,0.0833333,0.00019895,1167.73 +11,6,0.1,0.000207674,859.681 +11,6,0.116667,0.000329607,725.051 +11,6,0.133333,0.00025419,850.993 +11,6,0.15,0.000280903,1005.5 +11,6,0.166667,0.000264711,1573.66 +11,6,0.183333,0.000384249,1347.49 +11,6,0.2,0.000424453,1213.01 +11,6,0.216667,0.000530908,1139.78 +11,6,0.233333,0.000381805,1382.19 +11,6,0.25,0.000409468,925.382 +11,6,0.266667,0.000799394,1218.01 +11,6,0.283333,0.000538158,891.701 +11,6,0.3,0.000605419,1285.5 +11,6,0.316667,0.000610081,1123.39 +11,6,0.333333,0.000784285,990.258 +11,6,0.35,0.000861447,1060.75 +11,6,0.366667,0.000669995,1225.94 +11,6,0.383333,0.00119657,1239.81 +11,6,0.4,0.00088541,1260.36 +11,6,0.416667,0.000731341,1220.57 +11,6,0.433333,0.00118714,1409.22 +11,6,0.45,0.00103964,1628.14 +11,6,0.466667,0.00140385,923.207 +11,6,0.483333,0.00113288,1491.41 +11,6,0.5,0.00118136,1570.36 +11,6,0,0,1439 +11,6,0.0166667,3.57861e-05,839.553 +11,6,0.0333333,5.59774e-05,1141.13 +11,6,0.05,9.47169e-05,1236.56 +11,6,0.0666667,0.000148897,1344.25 +11,6,0.0833333,0.000201271,1949.27 +11,6,0.1,0.000271055,699.121 +11,6,0.116667,0.000233791,1606.88 +11,6,0.133333,0.00023373,911.944 +11,6,0.15,0.000291666,1479.89 +11,6,0.166667,0.000604711,870.197 +11,6,0.183333,0.000314732,1480.15 +11,6,0.2,0.000443234,1549.18 +11,6,0.216667,0.000350063,1084.83 +11,6,0.233333,0.000498017,434.998 +11,6,0.25,0.000449454,1593.34 +11,6,0.266667,0.000516754,1101.15 +11,6,0.283333,0.000944459,1899.18 +11,6,0.3,0.000556447,1572.12 +11,6,0.316667,0.000592288,799.654 +11,6,0.333333,0.00107302,1320.69 +11,6,0.35,0.000748185,1700.54 +11,6,0.366667,0.000905857,1126.06 +11,6,0.383333,0.000793151,1580.45 +11,6,0.4,0.000557242,1149.05 +11,6,0.416667,0.00110629,1688.05 +11,6,0.433333,0.00105384,1273.97 +11,6,0.45,0.00104343,883.738 +11,6,0.466667,0.00156733,1060.02 +11,6,0.483333,0.000771249,956.232 +11,6,0.5,0.00118778,1246.15 +11,6,0,0,1020.59 +11,6,0.0166667,2.58094e-05,1216.26 +11,6,0.0333333,7.37863e-05,925.366 +11,6,0.05,0.000154282,1503.53 +11,6,0.0666667,0.000136085,1670.44 +11,6,0.0833333,0.000246896,1257.87 +11,6,0.1,0.000223708,1630.66 +11,6,0.116667,0.000215315,1241.29 +11,6,0.133333,0.000329148,1189.32 +11,6,0.15,0.000270179,664.26 +11,6,0.166667,0.000284813,1473.73 +11,6,0.183333,0.000452019,1424.79 +11,6,0.2,0.000452478,1444.35 +11,6,0.216667,0.000430028,1528.01 +11,6,0.233333,0.00058578,1935.24 +11,6,0.25,0.000490752,1157.14 +11,6,0.266667,0.000623102,1480.11 +11,6,0.283333,0.000529263,1423.58 +11,6,0.3,0.000538431,872.758 +11,6,0.316667,0.00101648,1447.31 +11,6,0.333333,0.000703651,1354.12 +11,6,0.35,0.000653048,1709.59 +11,6,0.366667,0.000716279,1136.36 +11,6,0.383333,0.000717508,1720.97 +11,6,0.4,0.000697154,1198.45 +11,6,0.416667,0.00180247,1100.75 +11,6,0.433333,0.000901073,1142.97 +11,6,0.45,0.000904278,1585.53 +11,6,0.466667,0.000985466,1311.25 +11,6,0.483333,0.00105208,711.336 +11,6,0.5,0.00107766,1483.85 +11,6,0,0,1396.21 +11,6,0.0166667,3.57539e-05,1256.19 +11,6,0.0333333,8.57028e-05,1259.09 +11,6,0.05,0.000111102,1393.83 +11,6,0.0666667,0.000115432,1097.04 +11,6,0.0833333,0.000175255,1369.34 +11,6,0.1,0.000184007,1105.02 +11,6,0.116667,0.000235109,1665.46 +11,6,0.133333,0.000241328,1032.66 +11,6,0.15,0.000276124,1468.81 +11,6,0.166667,0.000395525,1681.05 +11,6,0.183333,0.000390064,1008.15 +11,6,0.2,0.000436743,1740.26 +11,6,0.216667,0.00065168,1349.43 +11,6,0.233333,0.000558361,1036 +11,6,0.25,0.000376531,1028.7 +11,6,0.266667,0.000632917,1112.98 +11,6,0.283333,0.000462167,1305.01 +11,6,0.3,0.000844015,1268.47 +11,6,0.316667,0.000684154,1084.82 +11,6,0.333333,0.000539652,917.309 +11,6,0.35,0.000889026,858.911 +11,6,0.366667,0.000891065,900.126 +11,6,0.383333,0.000850334,1438.4 +11,6,0.4,0.00115919,1398.15 +11,6,0.416667,0.000787461,1229.16 +11,6,0.433333,0.00168278,1497.78 +11,6,0.45,0.000687187,1020.48 +11,6,0.466667,0.000872814,966.06 +11,6,0.483333,0.00101665,1268.87 +11,6,0.5,0.000982157,1597.3 +9,7,0,0,1229.67 +9,7,0.0166667,4.05231e-05,1113.55 +9,7,0.0333333,0.000146492,1640.53 +9,7,0.05,0.000178497,1508.6 +9,7,0.0666667,0.000194158,1750.61 +9,7,0.0833333,0.00021812,1515.33 +9,7,0.1,0.000200024,1223.72 +9,7,0.116667,0.000612166,1471.39 +9,7,0.133333,0.000399572,1309.78 +9,7,0.15,0.000265954,1405.37 +9,7,0.166667,0.00057038,1153.99 +9,7,0.183333,0.000352934,1292.43 +9,7,0.2,0.000650623,1690.48 +9,7,0.216667,0.000472258,1552.45 +9,7,0.233333,0.000513151,1578.35 +9,7,0.25,0.000573914,1353.88 +9,7,0.266667,0.000484417,1407.81 +9,7,0.283333,0.00075623,1353.63 +9,7,0.3,0.000624834,942.582 +9,7,0.316667,0.00334119,1695.38 +9,7,0.333333,0.000579727,1419.75 +9,7,0.35,0.000615785,1095.5 +9,7,0.366667,0.00089301,1391.26 +9,7,0.383333,0.000896677,1145.39 +9,7,0.4,0.00108135,1612.71 +9,7,0.416667,0.00165253,771.261 +9,7,0.433333,0.000737208,1434.1 +9,7,0.45,0.00308503,1808.55 +9,7,0.466667,0.0011365,1266.64 +9,7,0.483333,0.00103878,1346.21 +9,7,0.5,0.00184963,1981.94 +9,7,0,0,1809.66 +9,7,0.0166667,4.24324e-05,1519.75 +9,7,0.0333333,8.65455e-05,1313.57 +9,7,0.05,0.000111559,1567.57 +9,7,0.0666667,0.000227537,1591.24 +9,7,0.0833333,0.000191042,1078.44 +9,7,0.1,0.000262351,1077.09 +9,7,0.116667,0.000418805,917.038 +9,7,0.133333,0.000299486,1054.23 +9,7,0.15,0.00183148,1534.05 +9,7,0.166667,0.00120299,1329.95 +9,7,0.183333,0.000365875,1768.57 +9,7,0.2,0.000417633,1141.54 +9,7,0.216667,0.00046056,1297.34 +9,7,0.233333,0.00105536,1526.36 +9,7,0.25,0.000563759,667.331 +9,7,0.266667,0.000649856,1438.99 +9,7,0.283333,0.00112715,1146.68 +9,7,0.3,0.000881294,1215.77 +9,7,0.316667,0.000977013,1376.53 +9,7,0.333333,0.00136772,1100.88 +9,7,0.35,0.000870488,1761.85 +9,7,0.366667,0.000994342,1528.43 +9,7,0.383333,0.000950147,1397.4 +9,7,0.4,0.000888141,1726.88 +9,7,0.416667,0.000817084,1325.2 +9,7,0.433333,0.0024567,1161.6 +9,7,0.45,0.00103745,1847.99 +9,7,0.466667,0.00288764,1229.42 +9,7,0.483333,0.00125887,1842.29 +9,7,0.5,0.00125749,1356 +9,7,0,0,1251.17 +9,7,0.0166667,4.2357e-05,1350.42 +9,7,0.0333333,8.25282e-05,1211.93 +9,7,0.05,0.000161842,1678.22 +9,7,0.0666667,0.00016244,1380.85 +9,7,0.0833333,0.000293727,1572.1 +9,7,0.1,0.000235774,1651.89 +9,7,0.116667,0.000293535,820.145 +9,7,0.133333,0.000288339,915.775 +9,7,0.15,0.000592487,1035.15 +9,7,0.166667,0.000662385,1906.9 +9,7,0.183333,0.000478974,727.979 +9,7,0.2,0.000540311,1426.02 +9,7,0.216667,0.000901895,1912.85 +9,7,0.233333,0.000577511,1579.99 +9,7,0.25,0.000748954,1518.26 +9,7,0.266667,0.0015649,1246.51 +9,7,0.283333,0.000654417,1519.53 +9,7,0.3,0.000507334,1249.63 +9,7,0.316667,0.000911953,1110.86 +9,7,0.333333,0.000550186,931.22 +9,7,0.35,0.000635658,838.619 +9,7,0.366667,0.000968128,1285.55 +9,7,0.383333,0.000841189,1036.32 +9,7,0.4,0.00279621,1670.63 +9,7,0.416667,0.000978121,1134.18 +9,7,0.433333,0.00170564,1200.65 +9,7,0.45,0.00118902,1445.27 +9,7,0.466667,0.000828205,1299.51 +9,7,0.483333,0.000992584,1639.13 +9,7,0.5,0.00113679,1071.56 +9,7,0,0,1192.17 +9,7,0.0166667,6.95521e-05,1467.3 +9,7,0.0333333,7.38993e-05,1523.27 +9,7,0.05,8.90026e-05,1527.79 +9,7,0.0666667,0.000203147,1636.68 +9,7,0.0833333,0.00020499,1458.82 +9,7,0.1,0.000216552,1668.54 +9,7,0.116667,0.00045501,1355.27 +9,7,0.133333,0.000553258,1697.16 +9,7,0.15,0.000438674,1594.03 +9,7,0.166667,0.000331905,1512.35 +9,7,0.183333,0.000605197,1582.71 +9,7,0.2,0.000396654,1426.46 +9,7,0.216667,0.000808087,1737.51 +9,7,0.233333,0.000547383,1032.5 +9,7,0.25,0.000755035,1276.83 +9,7,0.266667,0.000517179,1078.32 +9,7,0.283333,0.0011748,1591.53 +9,7,0.3,0.000971102,763.02 +9,7,0.316667,0.000989603,1790.69 +9,7,0.333333,0.000873123,1443.46 +9,7,0.35,0.000666624,1346.59 +9,7,0.366667,0.000879477,593.214 +9,7,0.383333,0.000722964,1657.52 +9,7,0.4,0.00126455,1193.93 +9,7,0.416667,0.0010123,1721.49 +9,7,0.433333,0.000873827,1006.31 +9,7,0.45,0.0012581,1586.26 +9,7,0.466667,0.00180654,1433.61 +9,7,0.483333,0.00102241,1138.01 +9,7,0.5,0.000945272,1273.52 +9,7,0,0,1176.05 +9,7,0.0166667,3.94352e-05,1583.66 +9,7,0.0333333,6.42898e-05,1304.15 +9,7,0.05,0.000113752,1018.63 +9,7,0.0666667,0.000165185,1457.93 +9,7,0.0833333,0.000154146,1443.45 +9,7,0.1,0.000351011,1696.6 +9,7,0.116667,0.000241144,1457.05 +9,7,0.133333,0.000408708,1487.48 +9,7,0.15,0.000622083,854.286 +9,7,0.166667,0.000464356,1354.22 +9,7,0.183333,0.00150462,1546.27 +9,7,0.2,0.00071482,1335.54 +9,7,0.216667,0.000575849,1242.43 +9,7,0.233333,0.000583204,1549.41 +9,7,0.25,0.000529009,1557.44 +9,7,0.266667,0.000386762,1190.45 +9,7,0.283333,0.000742091,1605.73 +9,7,0.3,0.000813956,1560.73 +9,7,0.316667,0.000914529,1976.91 +9,7,0.333333,0.000856547,1803.21 +9,7,0.35,0.00162126,1302.17 +9,7,0.366667,0.000876181,891.318 +9,7,0.383333,0.000913253,1176.99 +9,7,0.4,0.0012707,2051.13 +9,7,0.416667,0.00100614,1484.27 +9,7,0.433333,0.00181533,1519.06 +9,7,0.45,0.00299252,1452.6 +9,7,0.466667,0.00135761,1594.56 +9,7,0.483333,0.000937669,1416.62 +9,7,0.5,0.00117627,1417.77 +9,7,0,0,1847.88 +9,7,0.0166667,4.97037e-05,1300.57 +9,7,0.0333333,6.53488e-05,1213.84 +9,7,0.05,0.000135651,1569.94 +9,7,0.0666667,0.000242354,1657.51 +9,7,0.0833333,0.000183949,1118.51 +9,7,0.1,0.000368965,1388.6 +9,7,0.116667,0.000510839,1202.9 +9,7,0.133333,0.000532694,1487.81 +9,7,0.15,0.000409063,1166.1 +9,7,0.166667,0.000421686,1345.03 +9,7,0.183333,0.000672545,1271.81 +9,7,0.2,0.00055449,1797.23 +9,7,0.216667,0.000432333,1307.98 +9,7,0.233333,0.000505328,1572.26 +9,7,0.25,0.000591417,1526.78 +9,7,0.266667,0.00132354,1692.56 +9,7,0.283333,0.000551013,752.467 +9,7,0.3,0.000611921,1328.29 +9,7,0.316667,0.000589351,1614.09 +9,7,0.333333,0.000584239,1393.22 +9,7,0.35,0.000864302,1583.35 +9,7,0.366667,0.000865565,1689.25 +9,7,0.383333,0.000832219,1142.4 +9,7,0.4,0.00082175,1787.42 +9,7,0.416667,0.00199146,1636.75 +9,7,0.433333,0.000904511,1403.17 +9,7,0.45,0.00125411,1303.03 +9,7,0.466667,0.000706093,734.725 +9,7,0.483333,0.00155658,1422.78 +9,7,0.5,0.000897005,1242.06 +9,7,0,0,1596.15 +9,7,0.0166667,3.4964e-05,1753.85 +9,7,0.0333333,0.000191921,1325.08 +9,7,0.05,0.000151843,1113.77 +9,7,0.0666667,0.000155233,1567.01 +9,7,0.0833333,0.000165964,1152.93 +9,7,0.1,0.000188908,997.487 +9,7,0.116667,0.000339589,1324.44 +9,7,0.133333,0.000448688,1100.79 +9,7,0.15,0.000323958,1430.96 +9,7,0.166667,0.000741619,1508.79 +9,7,0.183333,0.000465555,1309.33 +9,7,0.2,0.000605988,1440.86 +9,7,0.216667,0.000519105,1450.22 +9,7,0.233333,0.000410031,1171.68 +9,7,0.25,0.000453373,1539 +9,7,0.266667,0.000466642,1502.15 +9,7,0.283333,0.000589397,1531.46 +9,7,0.3,0.00125281,1505.88 +9,7,0.316667,0.00103423,1022.68 +9,7,0.333333,0.00141839,1219.94 +9,7,0.35,0.000776094,858.612 +9,7,0.366667,0.00139165,1503.65 +9,7,0.383333,0.00117288,1537.37 +9,7,0.4,0.000810159,1466.29 +9,7,0.416667,0.00533437,1677.47 +9,7,0.433333,0.00110006,1966.28 +9,7,0.45,0.00122159,1495.85 +9,7,0.466667,0.00110563,1291.46 +9,7,0.483333,0.00110229,1031.74 +9,7,0.5,0.00220783,1010.37 +9,7,0,0,1911.78 +9,7,0.0166667,2.81767e-05,1401.48 +9,7,0.0333333,7.43107e-05,1368.33 +9,7,0.05,0.000213557,1655.12 +9,7,0.0666667,0.000103044,1409.54 +9,7,0.0833333,0.00024952,1008.94 +9,7,0.1,0.000416852,1556.89 +9,7,0.116667,0.000269882,1273.49 +9,7,0.133333,0.000301611,956.433 +9,7,0.15,0.000430876,1381.82 +9,7,0.166667,0.000433253,1558.52 +9,7,0.183333,0.000593705,1516.16 +9,7,0.2,0.00039317,1372.82 +9,7,0.216667,0.000580934,1124.63 +9,7,0.233333,0.000549013,1277.95 +9,7,0.25,0.000427765,1380.39 +9,7,0.266667,0.000460793,1638.21 +9,7,0.283333,0.000496283,1213.89 +9,7,0.3,0.000777288,1586.48 +9,7,0.316667,0.000558334,1327.69 +9,7,0.333333,0.000853353,1503.77 +9,7,0.35,0.00183749,1795.16 +9,7,0.366667,0.000769033,1377.37 +9,7,0.383333,0.00182383,1412.09 +9,7,0.4,0.00111417,1150.69 +9,7,0.416667,0.00128506,1578.31 +9,7,0.433333,0.000760198,1057.48 +9,7,0.45,0.000876477,1300.6 +9,7,0.466667,0.0011222,1488.46 +9,7,0.483333,0.00114877,1632.69 +9,7,0.5,0.00279837,1427.79 +9,7,0,0,1588.55 +9,7,0.0166667,4.94151e-05,1396.63 +9,7,0.0333333,7.7412e-05,1566.41 +9,7,0.05,0.000155134,1145.43 +9,7,0.0666667,0.000148352,1515.28 +9,7,0.0833333,0.000231507,1229.11 +9,7,0.1,0.000171852,1191.2 +9,7,0.116667,0.000227156,1832.71 +9,7,0.133333,0.000331713,1438.84 +9,7,0.15,0.000399366,558.839 +9,7,0.166667,0.000389553,1389.39 +9,7,0.183333,0.000633691,1679.84 +9,7,0.2,0.000716827,1290.9 +9,7,0.216667,0.000526269,1204.95 +9,7,0.233333,0.000873578,1203.32 +9,7,0.25,0.000611187,1649.11 +9,7,0.266667,0.000765428,1331.34 +9,7,0.283333,0.000662252,940.54 +9,7,0.3,0.000696433,1398.01 +9,7,0.316667,0.000600956,1296.3 +9,7,0.333333,0.000937577,1439.52 +9,7,0.35,0.000835776,1243.01 +9,7,0.366667,0.000772593,881.664 +9,7,0.383333,0.00078263,1425.25 +9,7,0.4,0.000785888,1105.79 +9,7,0.416667,0.00115381,859.221 +9,7,0.433333,0.00101804,1029.32 +9,7,0.45,0.00154756,1400.14 +9,7,0.466667,0.00144752,1437.51 +9,7,0.483333,0.00099508,1001.25 +9,7,0.5,0.000944219,995.352 +9,7,0,0,1337.71 +9,7,0.0166667,3.09528e-05,1163.25 +9,7,0.0333333,8.93882e-05,1538.4 +9,7,0.05,0.000214859,1699.88 +9,7,0.0666667,0.000174248,1273.37 +9,7,0.0833333,0.000190953,1926.53 +9,7,0.1,0.00126252,1356.12 +9,7,0.116667,0.000265048,1537.56 +9,7,0.133333,0.000378795,1643.24 +9,7,0.15,0.00038966,1566.73 +9,7,0.166667,0.000377269,1545.92 +9,7,0.183333,0.000344663,1827.32 +9,7,0.2,0.000714322,1537.67 +9,7,0.216667,0.000691671,1671.91 +9,7,0.233333,0.000501392,1021.66 +9,7,0.25,0.000576553,1290.4 +9,7,0.266667,0.000926572,1528.59 +9,7,0.283333,0.000654926,1604.22 +9,7,0.3,0.000578454,1252.52 +9,7,0.316667,0.000656855,1168.91 +9,7,0.333333,0.0011233,993.44 +9,7,0.35,0.00362455,1232.79 +9,7,0.366667,0.000919673,1679.88 +9,7,0.383333,0.000604043,1350.65 +9,7,0.4,0.000786049,1061.8 +9,7,0.416667,0.000821242,810.786 +9,7,0.433333,0.00184485,1386.58 +9,7,0.45,0.00201287,1319.46 +9,7,0.466667,0.000986127,1466.51 +9,7,0.483333,0.00162197,1745.17 +9,7,0.5,0.00207269,1361.92 +12,8,0,0,1535.88 +12,8,0.0166667,2.96915e-05,1108.96 +12,8,0.0333333,0.000119874,1811.66 +12,8,0.05,0.000113866,1295.79 +12,8,0.0666667,0.000164747,1400.55 +12,8,0.0833333,0.00022803,2121.16 +12,8,0.1,0.000548866,2053.7 +12,8,0.116667,0.000284165,1462.33 +12,8,0.133333,0.000292296,1303.45 +12,8,0.15,0.000335135,1839.22 +12,8,0.166667,0.000360844,1270.86 +12,8,0.183333,0.000373558,1469.55 +12,8,0.2,0.000620992,1413.96 +12,8,0.216667,0.000767369,1908.09 +12,8,0.233333,0.000586641,1589.11 +12,8,0.25,0.000594685,1384.53 +12,8,0.266667,0.000649389,1011.35 +12,8,0.283333,0.00054654,1588.63 +12,8,0.3,0.000596968,1321.93 +12,8,0.316667,0.00115691,1726.25 +12,8,0.333333,0.000794062,1364.84 +12,8,0.35,0.000665607,1437.41 +12,8,0.366667,0.000752518,1691.31 +12,8,0.383333,0.00159056,1341.22 +12,8,0.4,0.000778753,1550.82 +12,8,0.416667,0.000824027,1741.7 +12,8,0.433333,0.00130467,1168.44 +12,8,0.45,0.00102607,1594.48 +12,8,0.466667,0.00109457,888.176 +12,8,0.483333,0.00175781,1784.92 +12,8,0.5,0.00131879,1385.31 +12,8,0,0,1476.65 +12,8,0.0166667,2.71807e-05,1276.77 +12,8,0.0333333,8.1565e-05,1354.09 +12,8,0.05,0.000127559,1640.49 +12,8,0.0666667,0.000115893,1363 +12,8,0.0833333,0.000349667,1601.55 +12,8,0.1,0.000198907,1410.7 +12,8,0.116667,0.000289881,1451.08 +12,8,0.133333,0.000344355,1745.4 +12,8,0.15,0.000320034,1107.62 +12,8,0.166667,0.000398137,1608.11 +12,8,0.183333,0.000461667,1542.99 +12,8,0.2,0.000748262,1331.85 +12,8,0.216667,0.000609862,1935.49 +12,8,0.233333,0.000499122,1403.46 +12,8,0.25,0.000740351,1469.9 +12,8,0.266667,0.000798963,1607.86 +12,8,0.283333,0.00107695,1653.9 +12,8,0.3,0.000667484,1979.61 +12,8,0.316667,0.00113302,1212.41 +12,8,0.333333,0.00105741,1720.81 +12,8,0.35,0.000786705,1333.51 +12,8,0.366667,0.000806266,1195.36 +12,8,0.383333,0.000758363,1476.29 +12,8,0.4,0.000846373,1782.39 +12,8,0.416667,0.000897821,1782.08 +12,8,0.433333,0.00151866,1454.67 +12,8,0.45,0.00133634,1848.93 +12,8,0.466667,0.000969784,1353.78 +12,8,0.483333,0.00103134,867.031 +12,8,0.5,0.00125133,1173.61 +12,8,0,0,1462.59 +12,8,0.0166667,4.78054e-05,1184.34 +12,8,0.0333333,0.000101643,1649.66 +12,8,0.05,0.000112024,1675.31 +12,8,0.0666667,0.000157357,1235.9 +12,8,0.0833333,0.000231722,1748.46 +12,8,0.1,0.000188154,1257.92 +12,8,0.116667,0.000230423,1642.12 +12,8,0.133333,0.000255038,1451.37 +12,8,0.15,0.000443421,1855.37 +12,8,0.166667,0.000343977,1244.83 +12,8,0.183333,0.000340758,2131.18 +12,8,0.2,0.000641899,2067.92 +12,8,0.216667,0.000648103,2013.39 +12,8,0.233333,0.00074626,2283.98 +12,8,0.25,0.000580685,1269.95 +12,8,0.266667,0.000569016,1416.59 +12,8,0.283333,0.000533702,1622.43 +12,8,0.3,0.000827694,1620.83 +12,8,0.316667,0.000601578,1176.72 +12,8,0.333333,0.000666756,1617.69 +12,8,0.35,0.000780539,1296.22 +12,8,0.366667,0.0012304,1983.23 +12,8,0.383333,0.000578147,1169.13 +12,8,0.4,0.000989117,1629.24 +12,8,0.416667,0.00139503,1610.41 +12,8,0.433333,0.00122993,1733.52 +12,8,0.45,0.00124276,1656.99 +12,8,0.466667,0.00092412,1482.74 +12,8,0.483333,0.000886347,1834.21 +12,8,0.5,0.00133258,1911.1 +12,8,0,0,1525.93 +12,8,0.0166667,3.85103e-05,1546.33 +12,8,0.0333333,7.55228e-05,1467.5 +12,8,0.05,0.000176608,1572.28 +12,8,0.0666667,0.000136501,1675.53 +12,8,0.0833333,0.000288396,1734.74 +12,8,0.1,0.000360629,1900.02 +12,8,0.116667,0.000261998,1500.82 +12,8,0.133333,0.00035152,1356.82 +12,8,0.15,0.000307422,1878.24 +12,8,0.166667,0.000364791,1763.85 +12,8,0.183333,0.000472649,1503.18 +12,8,0.2,0.000532948,2067.17 +12,8,0.216667,0.000426799,1538.99 +12,8,0.233333,0.000661425,1453.5 +12,8,0.25,0.000690775,1608.91 +12,8,0.266667,0.000554294,1006.73 +12,8,0.283333,0.000612127,1533 +12,8,0.3,0.000649936,1571.99 +12,8,0.316667,0.000942931,1130.66 +12,8,0.333333,0.0010933,1382.35 +12,8,0.35,0.000988086,1734.83 +12,8,0.366667,0.000880784,698.933 +12,8,0.383333,0.00068888,1588.98 +12,8,0.4,0.00138512,1334.84 +12,8,0.416667,0.00111291,1750.87 +12,8,0.433333,0.0017774,1733.75 +12,8,0.45,0.000903747,1540.75 +12,8,0.466667,0.00094697,1923.16 +12,8,0.483333,0.000953834,1407.36 +12,8,0.5,0.000855865,1515.25 +12,8,0,0,1052.6 +12,8,0.0166667,4.05903e-05,1124.96 +12,8,0.0333333,6.34402e-05,1239.59 +12,8,0.05,0.000129035,2193 +12,8,0.0666667,0.000144656,1670.88 +12,8,0.0833333,0.000165753,1413.34 +12,8,0.1,0.000256092,1212.44 +12,8,0.116667,0.000365635,1773.38 +12,8,0.133333,0.000340382,1724.02 +12,8,0.15,0.000363226,784.582 +12,8,0.166667,0.00029998,949.93 +12,8,0.183333,0.000268807,1018.85 +12,8,0.2,0.000436702,1322.79 +12,8,0.216667,0.000614262,1575.4 +12,8,0.233333,0.000513432,1047.51 +12,8,0.25,0.00109785,1610.92 +12,8,0.266667,0.000689494,1700.75 +12,8,0.283333,0.000855734,1449 +12,8,0.3,0.00129623,1274.14 +12,8,0.316667,0.000955794,1506.75 +12,8,0.333333,0.00092688,1045.55 +12,8,0.35,0.000759291,1421.97 +12,8,0.366667,0.000725188,1773.69 +12,8,0.383333,0.000829008,1958.98 +12,8,0.4,0.00102861,1113.33 +12,8,0.416667,0.00109729,1752.7 +12,8,0.433333,0.00130566,1291.28 +12,8,0.45,0.00115844,1558.63 +12,8,0.466667,0.00124676,1471.38 +12,8,0.483333,0.00110567,1599.41 +12,8,0.5,0.00130001,1730.24 +12,8,0,0,1590.27 +12,8,0.0166667,3.79687e-05,1708.49 +12,8,0.0333333,8.79817e-05,1982.87 +12,8,0.05,0.000124216,1779 +12,8,0.0666667,0.000121022,1435.47 +12,8,0.0833333,0.000256839,1867.2 +12,8,0.1,0.000286486,1594.14 +12,8,0.116667,0.000286172,1526.26 +12,8,0.133333,0.000297144,1636.32 +12,8,0.15,0.000384539,1485.38 +12,8,0.166667,0.000564255,1370.59 +12,8,0.183333,0.000370263,1746.19 +12,8,0.2,0.000415042,1215.32 +12,8,0.216667,0.000583022,1453.88 +12,8,0.233333,0.000615681,1077.86 +12,8,0.25,0.00071551,1786.29 +12,8,0.266667,0.000606985,1656.53 +12,8,0.283333,0.000943778,1329.22 +12,8,0.3,0.000619217,1284.32 +12,8,0.316667,0.000829505,1340.71 +12,8,0.333333,0.00064447,1570.7 +12,8,0.35,0.0010903,1308.1 +12,8,0.366667,0.000792344,1517.55 +12,8,0.383333,0.00111977,1108.66 +12,8,0.4,0.000763553,866.371 +12,8,0.416667,0.00103271,1511.36 +12,8,0.433333,0.000765271,1063.23 +12,8,0.45,0.000671657,1442.61 +12,8,0.466667,0.00120351,1685.63 +12,8,0.483333,0.00143658,1508.52 +12,8,0.5,0.00131182,1411.89 +12,8,0,0,1388.25 +12,8,0.0166667,4.84761e-05,1816.6 +12,8,0.0333333,0.00010495,1730.99 +12,8,0.05,0.000105614,1213.77 +12,8,0.0666667,0.000197187,1402.22 +12,8,0.0833333,0.000159596,1525.26 +12,8,0.1,0.000307986,1452.71 +12,8,0.116667,0.0002056,1451.14 +12,8,0.133333,0.000325498,965.597 +12,8,0.15,0.000341778,1154.66 +12,8,0.166667,0.000415922,1717.9 +12,8,0.183333,0.000506645,2094.28 +12,8,0.2,0.000590459,1934.64 +12,8,0.216667,0.000431867,1600.78 +12,8,0.233333,0.00102585,1621.61 +12,8,0.25,0.000850187,1808.8 +12,8,0.266667,0.000703876,1346.05 +12,8,0.283333,0.00107316,1764.33 +12,8,0.3,0.000553059,1412.36 +12,8,0.316667,0.000722643,1564.18 +12,8,0.333333,0.000668306,1267.58 +12,8,0.35,0.000811416,1044.79 +12,8,0.366667,0.000764694,1454.54 +12,8,0.383333,0.000946496,1771.89 +12,8,0.4,0.00151411,1603.61 +12,8,0.416667,0.000967672,1371.63 +12,8,0.433333,0.00147177,2046.9 +12,8,0.45,0.000964061,1426.29 +12,8,0.466667,0.00116741,1730.94 +12,8,0.483333,0.00141569,1325.3 +12,8,0.5,0.00131871,963.459 +12,8,0,0,1643.19 +12,8,0.0166667,4.72909e-05,1609.87 +12,8,0.0333333,0.000116672,1219.7 +12,8,0.05,0.000112476,1933.88 +12,8,0.0666667,0.000164759,1676.35 +12,8,0.0833333,0.000226783,1880.59 +12,8,0.1,0.00021407,1683.16 +12,8,0.116667,0.000236792,1238.25 +12,8,0.133333,0.000240622,1685.87 +12,8,0.15,0.000313695,1274.46 +12,8,0.166667,0.000380541,1574.25 +12,8,0.183333,0.00038774,1996.97 +12,8,0.2,0.00044359,1619.9 +12,8,0.216667,0.00039786,1310.98 +12,8,0.233333,0.000507247,1456.9 +12,8,0.25,0.00046598,1708.12 +12,8,0.266667,0.000467007,1344 +12,8,0.283333,0.000729498,1527.27 +12,8,0.3,0.000708506,1400.18 +12,8,0.316667,0.0009644,1465.96 +12,8,0.333333,0.000578129,1458.49 +12,8,0.35,0.00124532,1855.36 +12,8,0.366667,0.000673829,1449.03 +12,8,0.383333,0.000766635,1373.23 +12,8,0.4,0.00096229,1311.28 +12,8,0.416667,0.000915631,1806.52 +12,8,0.433333,0.000934504,1327.01 +12,8,0.45,0.000978801,1616.7 +12,8,0.466667,0.00135046,1443.17 +12,8,0.483333,0.000964447,1266.39 +12,8,0.5,0.000986737,1449.33 +12,8,0,0,1515.24 +12,8,0.0166667,3.1742e-05,1787.21 +12,8,0.0333333,7.43708e-05,1507.9 +12,8,0.05,8.119e-05,1291.27 +12,8,0.0666667,0.000121941,1331.69 +12,8,0.0833333,0.00036667,1664.49 +12,8,0.1,0.00025628,1607.65 +12,8,0.116667,0.000238978,1810.36 +12,8,0.133333,0.000307075,1573.93 +12,8,0.15,0.000345907,1868.97 +12,8,0.166667,0.000328605,1691.9 +12,8,0.183333,0.000346642,1363.12 +12,8,0.2,0.000530794,1385.37 +12,8,0.216667,0.000498771,1759.76 +12,8,0.233333,0.000581768,1578.27 +12,8,0.25,0.000767239,1212.47 +12,8,0.266667,0.000490207,1760.01 +12,8,0.283333,0.00093092,1421.7 +12,8,0.3,0.00077692,1451.82 +12,8,0.316667,0.000576394,1377.92 +12,8,0.333333,0.00110283,1414.3 +12,8,0.35,0.000625754,1344.54 +12,8,0.366667,0.000704696,1278.6 +12,8,0.383333,0.000815401,1474.28 +12,8,0.4,0.00101439,1728.17 +12,8,0.416667,0.00139957,1570.52 +12,8,0.433333,0.00155721,1680.78 +12,8,0.45,0.000864656,1855.4 +12,8,0.466667,0.000907975,1937.91 +12,8,0.483333,0.00102163,1771.85 +12,8,0.5,0.00123485,1433.93 +12,8,0,0,1102.66 +12,8,0.0166667,3.44006e-05,1774.64 +12,8,0.0333333,7.6196e-05,1691.02 +12,8,0.05,0.000148974,1269.63 +12,8,0.0666667,0.000110195,1384.21 +12,8,0.0833333,0.000154268,1710.24 +12,8,0.1,0.000320374,1716.12 +12,8,0.116667,0.000519478,1463.64 +12,8,0.133333,0.000279009,1726.73 +12,8,0.15,0.00038953,1177.66 +12,8,0.166667,0.00027983,1311.84 +12,8,0.183333,0.000476279,1377.09 +12,8,0.2,0.000839074,1719.93 +12,8,0.216667,0.000636253,1599.95 +12,8,0.233333,0.000622818,1419.44 +12,8,0.25,0.000531211,2024.11 +12,8,0.266667,0.000696849,1753.7 +12,8,0.283333,0.000589469,1334.42 +12,8,0.3,0.000641418,1400.16 +12,8,0.316667,0.000712487,1560.12 +12,8,0.333333,0.000835386,1410.05 +12,8,0.35,0.000843918,1101.81 +12,8,0.366667,0.0005723,1305.54 +12,8,0.383333,0.000749256,1776.9 +12,8,0.4,0.00109594,1844.89 +12,8,0.416667,0.00117151,1834.64 +12,8,0.433333,0.00161971,1469.38 +12,8,0.45,0.000910039,1443.13 +12,8,0.466667,0.00170146,1731.39 +12,8,0.483333,0.00190495,1654.73 +12,8,0.5,0.000973669,1715.35 +15,9,0,0,1573.12 +15,9,0.0166667,9.14734e-05,2100.75 +15,9,0.0333333,7.92446e-05,1332.14 +15,9,0.05,9.47879e-05,1055.88 +15,9,0.0666667,0.000145528,1949.5 +15,9,0.0833333,0.000149932,1382.31 +15,9,0.1,0.000176375,1700.72 +15,9,0.116667,0.000253677,1844.77 +15,9,0.133333,0.000391961,1565.75 +15,9,0.15,0.000388019,1351.59 +15,9,0.166667,0.000400584,1702.3 +15,9,0.183333,0.000403002,1952.95 +15,9,0.2,0.000397188,1572.86 +15,9,0.216667,0.000491407,1805.82 +15,9,0.233333,0.000713388,1828.75 +15,9,0.25,0.000604912,1863.66 +15,9,0.266667,0.000494386,1700.06 +15,9,0.283333,0.000672162,1375.34 +15,9,0.3,0.000942297,2016.73 +15,9,0.316667,0.000734956,1802.92 +15,9,0.333333,0.000656191,1204.23 +15,9,0.35,0.00125536,1590.17 +15,9,0.366667,0.000768433,1980.39 +15,9,0.383333,0.000782338,1909.75 +15,9,0.4,0.00131618,1511.74 +15,9,0.416667,0.00105177,1074.55 +15,9,0.433333,0.00219204,1702.2 +15,9,0.45,0.00126616,1664.82 +15,9,0.466667,0.00108956,1900.39 +15,9,0.483333,0.00292854,1554.95 +15,9,0.5,0.00159568,1370.09 +15,9,0,0,1177.2 +15,9,0.0166667,3.16777e-05,1584.5 +15,9,0.0333333,9.74926e-05,1844.02 +15,9,0.05,0.000129167,1433.46 +15,9,0.0666667,0.000209558,1798.37 +15,9,0.0833333,0.000180333,1635.96 +15,9,0.1,0.000259592,1502.66 +15,9,0.116667,0.000242755,1650.3 +15,9,0.133333,0.000375297,1326.5 +15,9,0.15,0.00052105,1557.67 +15,9,0.166667,0.000361427,1421.06 +15,9,0.183333,0.000446794,1312.88 +15,9,0.2,0.000609419,1292.39 +15,9,0.216667,0.000465466,1307.94 +15,9,0.233333,0.000567789,1464.43 +15,9,0.25,0.000583696,1979.39 +15,9,0.266667,0.000619462,1756.34 +15,9,0.283333,0.00069593,1744.19 +15,9,0.3,0.000738094,1859.36 +15,9,0.316667,0.000793133,2066.24 +15,9,0.333333,0.000920619,1616.03 +15,9,0.35,0.000763011,1594.37 +15,9,0.366667,0.00122781,1769.22 +15,9,0.383333,0.000800123,1402.97 +15,9,0.4,0.000893038,1858.45 +15,9,0.416667,0.000997031,1750.76 +15,9,0.433333,0.00153935,1595.79 +15,9,0.45,0.00163141,1330.63 +15,9,0.466667,0.000931719,1412.66 +15,9,0.483333,0.00104881,1421.07 +15,9,0.5,0.00127758,1874.47 +15,9,0,0,2065.23 +15,9,0.0166667,3.88166e-05,1852.86 +15,9,0.0333333,6.32707e-05,1732.45 +15,9,0.05,0.000129071,1479.62 +15,9,0.0666667,0.000127348,1135.53 +15,9,0.0833333,0.000145601,1230.12 +15,9,0.1,0.000250971,1580.32 +15,9,0.116667,0.000200198,1443.78 +15,9,0.133333,0.000397499,1811.6 +15,9,0.15,0.000292513,1564.41 +15,9,0.166667,0.000380204,1842.93 +15,9,0.183333,0.000348916,1325.06 +15,9,0.2,0.000459472,1655.98 +15,9,0.216667,0.000705709,1515.78 +15,9,0.233333,0.000790792,1213.25 +15,9,0.25,0.000507743,1433.74 +15,9,0.266667,0.000581186,1937.52 +15,9,0.283333,0.000658754,1983.39 +15,9,0.3,0.000803843,1593.35 +15,9,0.316667,0.00066552,1305.16 +15,9,0.333333,0.000652891,1815.71 +15,9,0.35,0.000804817,2413.1 +15,9,0.366667,0.000710196,1400.85 +15,9,0.383333,0.000951963,1631.13 +15,9,0.4,0.000948922,1074.38 +15,9,0.416667,0.000908878,1562.89 +15,9,0.433333,0.000895685,1818.97 +15,9,0.45,0.000913192,2133.02 +15,9,0.466667,0.000783591,1598.74 +15,9,0.483333,0.000996251,1679.29 +15,9,0.5,0.00134745,2007.97 +15,9,0,0,2051.04 +15,9,0.0166667,3.75495e-05,1895.02 +15,9,0.0333333,7.68012e-05,1183.14 +15,9,0.05,0.000102517,1465.74 +15,9,0.0666667,0.000148563,1497.41 +15,9,0.0833333,0.000163349,1873.68 +15,9,0.1,0.000203761,1495.31 +15,9,0.116667,0.000271068,1663.7 +15,9,0.133333,0.000340596,1604.59 +15,9,0.15,0.000410921,1691.21 +15,9,0.166667,0.000447925,1594.08 +15,9,0.183333,0.000640031,1821.24 +15,9,0.2,0.000444819,2149.3 +15,9,0.216667,0.000620273,1565.18 +15,9,0.233333,0.000398619,1527.38 +15,9,0.25,0.000576926,1444.54 +15,9,0.266667,0.00085258,1710.87 +15,9,0.283333,0.000705859,1451.51 +15,9,0.3,0.000835405,1818.35 +15,9,0.316667,0.000532821,1215.01 +15,9,0.333333,0.000597353,1392.42 +15,9,0.35,0.000952393,1646.78 +15,9,0.366667,0.000872309,1982.38 +15,9,0.383333,0.000886345,1922.47 +15,9,0.4,0.000869337,1910.96 +15,9,0.416667,0.000934993,1732.91 +15,9,0.433333,0.00117245,1734.14 +15,9,0.45,0.000953017,1429.78 +15,9,0.466667,0.00114841,1943.52 +15,9,0.483333,0.00100774,1538.57 +15,9,0.5,0.00137506,1877.11 +15,9,0,0,1454.76 +15,9,0.0166667,4.41707e-05,1318.91 +15,9,0.0333333,6.52205e-05,1547.75 +15,9,0.05,0.000121226,1239.44 +15,9,0.0666667,0.000152825,1562.62 +15,9,0.0833333,0.000187391,1783.82 +15,9,0.1,0.000230071,1389.39 +15,9,0.116667,0.00031508,1933.88 +15,9,0.133333,0.000239623,1635.02 +15,9,0.15,0.000565181,1377.6 +15,9,0.166667,0.000283117,1507.09 +15,9,0.183333,0.000406144,1671.87 +15,9,0.2,0.000446842,1419.85 +15,9,0.216667,0.000435062,1317.02 +15,9,0.233333,0.000477094,1677.59 +15,9,0.25,0.000482699,1335.89 +15,9,0.266667,0.00105174,1232.25 +15,9,0.283333,0.000536319,1791.54 +15,9,0.3,0.000611245,1131.77 +15,9,0.316667,0.000633014,1831 +15,9,0.333333,0.000729647,1752.94 +15,9,0.35,0.000679673,1372.18 +15,9,0.366667,0.000706719,1665.84 +15,9,0.383333,0.00137269,1313.71 +15,9,0.4,0.00091142,1777.6 +15,9,0.416667,0.00109065,1786.39 +15,9,0.433333,0.00096442,1298.97 +15,9,0.45,0.000900645,1480.83 +15,9,0.466667,0.00102366,1720.16 +15,9,0.483333,0.00128783,1746.51 +15,9,0.5,0.000907012,1373.39 +15,9,0,0,1556.72 +15,9,0.0166667,3.09733e-05,1797.25 +15,9,0.0333333,9.69984e-05,1912.43 +15,9,0.05,0.000111419,2023.58 +15,9,0.0666667,0.000161955,1507.71 +15,9,0.0833333,0.000188763,1548.85 +15,9,0.1,0.000210362,1334.63 +15,9,0.116667,0.000374086,1846.78 +15,9,0.133333,0.00032281,1843.03 +15,9,0.15,0.000372947,1610.65 +15,9,0.166667,0.000417645,1790.56 +15,9,0.183333,0.000425434,1689.54 +15,9,0.2,0.000361083,1625.47 +15,9,0.216667,0.000453426,1699.96 +15,9,0.233333,0.000554107,975.708 +15,9,0.25,0.000816144,1493.51 +15,9,0.266667,0.00054432,1995.02 +15,9,0.283333,0.000644692,1113.16 +15,9,0.3,0.000676488,1437.44 +15,9,0.316667,0.000820389,2048.39 +15,9,0.333333,0.000926089,1775.04 +15,9,0.35,0.00125972,1626.63 +15,9,0.366667,0.000602931,1536.34 +15,9,0.383333,0.000830356,1455.63 +15,9,0.4,0.00102143,1678.49 +15,9,0.416667,0.00158461,2053.28 +15,9,0.433333,0.00092422,2135.29 +15,9,0.45,0.00111136,1412.93 +15,9,0.466667,0.0011785,1279.49 +15,9,0.483333,0.00137327,1190.13 +15,9,0.5,0.00111647,1370.31 +15,9,0,0,1562.85 +15,9,0.0166667,5.18301e-05,1497.22 +15,9,0.0333333,8.98572e-05,1691.17 +15,9,0.05,0.000118253,1521.78 +15,9,0.0666667,0.000169855,922.215 +15,9,0.0833333,0.000189454,1758.28 +15,9,0.1,0.000227903,830.188 +15,9,0.116667,0.000427926,1841.9 +15,9,0.133333,0.000377309,1757.69 +15,9,0.15,0.000279124,1425.76 +15,9,0.166667,0.000298942,1512.89 +15,9,0.183333,0.000378569,1745.19 +15,9,0.2,0.000447346,1712.38 +15,9,0.216667,0.000765053,1858.92 +15,9,0.233333,0.00052104,1416.9 +15,9,0.25,0.000505649,1497.92 +15,9,0.266667,0.000795358,1701.51 +15,9,0.283333,0.000787858,1334.95 +15,9,0.3,0.00108635,1806.25 +15,9,0.316667,0.000674691,2117.13 +15,9,0.333333,0.000772425,1366.69 +15,9,0.35,0.000583834,1584.1 +15,9,0.366667,0.00096549,1903.68 +15,9,0.383333,0.000989658,1373.89 +15,9,0.4,0.000964429,1657.03 +15,9,0.416667,0.000863153,1619 +15,9,0.433333,0.000934581,1371.32 +15,9,0.45,0.00120212,1839.32 +15,9,0.466667,0.000845829,1576.81 +15,9,0.483333,0.00119785,1692.59 +15,9,0.5,0.00161073,1563.26 +15,9,0,0,1286.21 +15,9,0.0166667,3.19425e-05,1889.9 +15,9,0.0333333,8.19358e-05,1952.52 +15,9,0.05,0.000104348,1519.61 +15,9,0.0666667,0.000182576,1600.02 +15,9,0.0833333,0.000157976,1762.01 +15,9,0.1,0.000228203,1712.77 +15,9,0.116667,0.000395197,1452.31 +15,9,0.133333,0.000505579,1811.08 +15,9,0.15,0.000327983,1731.85 +15,9,0.166667,0.000419625,1498.72 +15,9,0.183333,0.000378167,1973.18 +15,9,0.2,0.000381884,1484.18 +15,9,0.216667,0.000616421,1759.12 +15,9,0.233333,0.000459057,1733.44 +15,9,0.25,0.000636837,1435.77 +15,9,0.266667,0.000511788,1308.96 +15,9,0.283333,0.000815736,1772.63 +15,9,0.3,0.000805828,2019.93 +15,9,0.316667,0.000859525,2050.18 +15,9,0.333333,0.000791453,1630.21 +15,9,0.35,0.000798091,1449.62 +15,9,0.366667,0.000873009,1594.54 +15,9,0.383333,0.00088134,1609.69 +15,9,0.4,0.000885885,1949.08 +15,9,0.416667,0.000912568,1436.77 +15,9,0.433333,0.000929936,1105.69 +15,9,0.45,0.000980261,1744.66 +15,9,0.466667,0.000867128,1338.81 +15,9,0.483333,0.00128229,1408.96 +15,9,0.5,0.0010802,1670.56 +15,9,0,0,1725.2 +15,9,0.0166667,3.54853e-05,1586.47 +15,9,0.0333333,6.36409e-05,1614.16 +15,9,0.05,0.000156605,1618.31 +15,9,0.0666667,0.000132926,1247.54 +15,9,0.0833333,0.000183372,1194.6 +15,9,0.1,0.0002282,1606.46 +15,9,0.116667,0.000216465,1583.6 +15,9,0.133333,0.000282834,858.688 +15,9,0.15,0.000251592,1808.49 +15,9,0.166667,0.0003731,1753.41 +15,9,0.183333,0.000340842,1262.62 +15,9,0.2,0.00049447,1773.65 +15,9,0.216667,0.000565365,1317.81 +15,9,0.233333,0.000497433,1437.9 +15,9,0.25,0.000823306,1559.94 +15,9,0.266667,0.000487035,913.155 +15,9,0.283333,0.000783655,1694.66 +15,9,0.3,0.000896776,1676.2 +15,9,0.316667,0.000718434,1779.11 +15,9,0.333333,0.000868452,1836.24 +15,9,0.35,0.000849111,1821.25 +15,9,0.366667,0.000797527,1434.96 +15,9,0.383333,0.000729351,1471.7 +15,9,0.4,0.00131951,1138.57 +15,9,0.416667,0.000792673,1887.3 +15,9,0.433333,0.00101552,1462.16 +15,9,0.45,0.00128124,1220.56 +15,9,0.466667,0.000873862,967.944 +15,9,0.483333,0.00104922,1540.24 +15,9,0.5,0.000894452,1588.84 +15,9,0,0,1266.95 +15,9,0.0166667,3.09685e-05,1997.29 +15,9,0.0333333,0.000107574,1996.4 +15,9,0.05,0.000141993,1370.21 +15,9,0.0666667,0.000138385,1422.76 +15,9,0.0833333,0.000221878,2114.26 +15,9,0.1,0.000308536,1909.64 +15,9,0.116667,0.000251283,1661.93 +15,9,0.133333,0.000290047,1969.33 +15,9,0.15,0.000335618,1380.73 +15,9,0.166667,0.000394159,1520.33 +15,9,0.183333,0.000390361,1800.22 +15,9,0.2,0.000425081,1930.38 +15,9,0.216667,0.000468739,1504.86 +15,9,0.233333,0.000445373,1954.64 +15,9,0.25,0.000764083,2022.76 +15,9,0.266667,0.000605028,2130.08 +15,9,0.283333,0.000633895,1919.83 +15,9,0.3,0.000584233,1413.21 +15,9,0.316667,0.000654123,1684.8 +15,9,0.333333,0.000705827,1817.05 +15,9,0.35,0.000986739,1589.42 +15,9,0.366667,0.000879165,1707.36 +15,9,0.383333,0.000810652,1873.87 +15,9,0.4,0.000832675,1550.88 +15,9,0.416667,0.000930547,1532.68 +15,9,0.433333,0.000893687,1637.7 +15,9,0.45,0.00108979,1752.52 +15,9,0.466667,0.00107708,1707.57 +15,9,0.483333,0.000979337,1417.29 +15,9,0.5,0.00140254,1674.6 +15,10,0,0,1715.88 +15,10,0.0166667,5.21926e-05,1317.38 +15,10,0.0333333,6.27249e-05,1347.27 +15,10,0.05,0.000109184,1157.29 +15,10,0.0666667,0.000158974,1918.01 +15,10,0.0833333,0.000240101,1442.03 +15,10,0.1,0.000240559,1906.91 +15,10,0.116667,0.000294322,2100.87 +15,10,0.133333,0.0003535,1823.03 +15,10,0.15,0.000371756,1302.14 +15,10,0.166667,0.000407856,1152.76 +15,10,0.183333,0.000407784,1683.32 +15,10,0.2,0.000415095,1530.39 +15,10,0.216667,0.000571323,1093.44 +15,10,0.233333,0.000438213,1797.42 +15,10,0.25,0.000719182,1284.29 +15,10,0.266667,0.00073114,1894.74 +15,10,0.283333,0.00125882,2034.05 +15,10,0.3,0.000800417,1887.06 +15,10,0.316667,0.000720069,1940.4 +15,10,0.333333,0.000666375,1545.93 +15,10,0.35,0.000954241,1412.22 +15,10,0.366667,0.000735402,1833.56 +15,10,0.383333,0.00130505,1492.85 +15,10,0.4,0.000970638,1714.52 +15,10,0.416667,0.00114123,1916.31 +15,10,0.433333,0.00126397,1690 +15,10,0.45,0.00146643,1767.15 +15,10,0.466667,0.00118557,1977.39 +15,10,0.483333,0.00171217,1667.91 +15,10,0.5,0.000996121,1327.17 +15,10,0,0,2314.02 +15,10,0.0166667,5.42492e-05,1925.83 +15,10,0.0333333,0.000107801,1661.32 +15,10,0.05,0.000110789,1545.31 +15,10,0.0666667,0.000164064,1737.81 +15,10,0.0833333,0.000151952,1504.9 +15,10,0.1,0.000412567,1782.83 +15,10,0.116667,0.000243683,1590.18 +15,10,0.133333,0.000425733,1653.53 +15,10,0.15,0.000378125,1716.54 +15,10,0.166667,0.000328866,1470.87 +15,10,0.183333,0.000313398,1206.43 +15,10,0.2,0.000549368,1239.58 +15,10,0.216667,0.000605857,1715.17 +15,10,0.233333,0.000418103,1670.42 +15,10,0.25,0.000555468,1620.01 +15,10,0.266667,0.000532779,2010.37 +15,10,0.283333,0.000657122,1765.21 +15,10,0.3,0.000816491,1549.6 +15,10,0.316667,0.000954014,1572.69 +15,10,0.333333,0.000646766,1474.25 +15,10,0.35,0.000912267,1565.45 +15,10,0.366667,0.000775599,1845.03 +15,10,0.383333,0.000660685,1562.55 +15,10,0.4,0.00102808,1763.15 +15,10,0.416667,0.0010074,1813.16 +15,10,0.433333,0.000973385,2155.18 +15,10,0.45,0.00144215,1462.81 +15,10,0.466667,0.0012219,2227.7 +15,10,0.483333,0.000953204,1894.06 +15,10,0.5,0.000959374,1652.01 +15,10,0,0,1308.17 +15,10,0.0166667,5.28109e-05,1787.45 +15,10,0.0333333,8.2191e-05,2110.13 +15,10,0.05,0.000134884,1735.24 +15,10,0.0666667,0.000189854,1283.78 +15,10,0.0833333,0.00016764,1725.15 +15,10,0.1,0.000277165,1978.19 +15,10,0.116667,0.000262028,1854.61 +15,10,0.133333,0.000267091,1870.61 +15,10,0.15,0.000299135,1638.47 +15,10,0.166667,0.000410327,1814.43 +15,10,0.183333,0.000444503,2018.91 +15,10,0.2,0.000374813,1149.31 +15,10,0.216667,0.000404022,1716.11 +15,10,0.233333,0.000547806,966.035 +15,10,0.25,0.000678712,2238.96 +15,10,0.266667,0.000929246,1645.89 +15,10,0.283333,0.000845038,1521.47 +15,10,0.3,0.000544433,1693.83 +15,10,0.316667,0.00137472,1756.87 +15,10,0.333333,0.000928607,1543.04 +15,10,0.35,0.000989625,1708.5 +15,10,0.366667,0.000912814,1936.18 +15,10,0.383333,0.0011379,1971.79 +15,10,0.4,0.00114475,2096.65 +15,10,0.416667,0.00111452,1734.43 +15,10,0.433333,0.000921335,1342.33 +15,10,0.45,0.00123978,1358.11 +15,10,0.466667,0.000902165,1734.98 +15,10,0.483333,0.00189527,2055.29 +15,10,0.5,0.00103944,1814.74 +15,10,0,0,1819.21 +15,10,0.0166667,3.08245e-05,1572.26 +15,10,0.0333333,7.30667e-05,1795.26 +15,10,0.05,0.000136819,1547.67 +15,10,0.0666667,0.000176348,1283.71 +15,10,0.0833333,0.000380884,2039.56 +15,10,0.1,0.000238488,940.14 +15,10,0.116667,0.000288416,1786.92 +15,10,0.133333,0.000477779,1171.39 +15,10,0.15,0.000344201,1724.03 +15,10,0.166667,0.000418987,1390.73 +15,10,0.183333,0.000389821,1922.17 +15,10,0.2,0.000354994,1439.12 +15,10,0.216667,0.000593604,1801.36 +15,10,0.233333,0.000741605,1620.8 +15,10,0.25,0.000554195,1264.78 +15,10,0.266667,0.000550705,1762.36 +15,10,0.283333,0.000594731,1927.91 +15,10,0.3,0.0010395,1635.26 +15,10,0.316667,0.000557152,1355.27 +15,10,0.333333,0.000695459,1820.27 +15,10,0.35,0.000745901,1649.76 +15,10,0.366667,0.000798452,1915.45 +15,10,0.383333,0.000749348,1584.46 +15,10,0.4,0.000900561,1498.22 +15,10,0.416667,0.000898797,1671.68 +15,10,0.433333,0.00107325,1125.7 +15,10,0.45,0.00108436,1827.34 +15,10,0.466667,0.00135574,1720.01 +15,10,0.483333,0.00122885,2024.06 +15,10,0.5,0.000940133,1721.46 +15,10,0,0,1575.14 +15,10,0.0166667,4.58312e-05,1730.4 +15,10,0.0333333,0.00011763,2297.64 +15,10,0.05,0.000138182,1530.04 +15,10,0.0666667,0.000133479,1873.25 +15,10,0.0833333,0.000214912,1428.28 +15,10,0.1,0.000207743,1442.2 +15,10,0.116667,0.000260673,1553.51 +15,10,0.133333,0.00060495,1625.58 +15,10,0.15,0.000299149,2083.62 +15,10,0.166667,0.000424739,1875.2 +15,10,0.183333,0.000379757,1706.14 +15,10,0.2,0.000743736,1777.14 +15,10,0.216667,0.000403526,1882.63 +15,10,0.233333,0.000590475,1722.84 +15,10,0.25,0.000661009,1895.92 +15,10,0.266667,0.000632303,1860.38 +15,10,0.283333,0.000566949,1839.69 +15,10,0.3,0.000635801,2033.39 +15,10,0.316667,0.00061893,1615.69 +15,10,0.333333,0.000782955,1926.75 +15,10,0.35,0.000892915,1618.98 +15,10,0.366667,0.00107149,1551.63 +15,10,0.383333,0.00147073,1892.24 +15,10,0.4,0.00149964,2099.52 +15,10,0.416667,0.00103488,1760.36 +15,10,0.433333,0.000875207,1676.17 +15,10,0.45,0.00113738,1709.97 +15,10,0.466667,0.00116226,1284.9 +15,10,0.483333,0.00101514,1564.17 +15,10,0.5,0.00115225,1658.82 +15,10,0,0,1560.25 +15,10,0.0166667,4.52069e-05,1864.05 +15,10,0.0333333,7.76459e-05,1252.89 +15,10,0.05,0.000188018,1331.66 +15,10,0.0666667,0.000139148,1969.38 +15,10,0.0833333,0.000171159,1704.42 +15,10,0.1,0.000385875,1451.06 +15,10,0.116667,0.000406267,1631.13 +15,10,0.133333,0.000324646,1612.59 +15,10,0.15,0.000296017,1376.06 +15,10,0.166667,0.000377847,1282.8 +15,10,0.183333,0.000345229,1437.93 +15,10,0.2,0.000798876,1683.54 +15,10,0.216667,0.000508929,1382.17 +15,10,0.233333,0.000691661,1632.65 +15,10,0.25,0.000601732,2063.07 +15,10,0.266667,0.000593973,1730.42 +15,10,0.283333,0.00058806,1659.55 +15,10,0.3,0.000855336,2282.99 +15,10,0.316667,0.000784436,1075.88 +15,10,0.333333,0.000676896,1477.48 +15,10,0.35,0.00075369,1953.96 +15,10,0.366667,0.000685895,1979.63 +15,10,0.383333,0.000818616,1925.46 +15,10,0.4,0.000816168,1827.97 +15,10,0.416667,0.00103489,1700.68 +15,10,0.433333,0.00110325,2000.36 +15,10,0.45,0.00108816,1425.16 +15,10,0.466667,0.00167058,1494.99 +15,10,0.483333,0.00197595,1931.55 +15,10,0.5,0.00103279,1748.17 +15,10,0,0,1407.29 +15,10,0.0166667,3.48058e-05,1663.77 +15,10,0.0333333,8.94666e-05,1783.55 +15,10,0.05,0.000102802,1555.27 +15,10,0.0666667,0.000147335,1062.86 +15,10,0.0833333,0.00034873,1817.7 +15,10,0.1,0.000221776,2242.18 +15,10,0.116667,0.000206602,1529.62 +15,10,0.133333,0.000335348,1329.92 +15,10,0.15,0.000444551,1696.74 +15,10,0.166667,0.000338498,1925.49 +15,10,0.183333,0.000556177,1658.83 +15,10,0.2,0.000410733,1910.38 +15,10,0.216667,0.000627454,1778.69 +15,10,0.233333,0.000496317,1731.86 +15,10,0.25,0.0010834,1662.04 +15,10,0.266667,0.000558633,1946.85 +15,10,0.283333,0.00107281,1498.84 +15,10,0.3,0.000998221,1749.55 +15,10,0.316667,0.000686723,1475.43 +15,10,0.333333,0.000636714,1566.51 +15,10,0.35,0.000815026,1963.91 +15,10,0.366667,0.000885016,1472.35 +15,10,0.383333,0.00112678,1907.94 +15,10,0.4,0.0010696,1467.83 +15,10,0.416667,0.00153385,1895.97 +15,10,0.433333,0.00101333,1896.9 +15,10,0.45,0.00150596,1569.48 +15,10,0.466667,0.00108867,1664.27 +15,10,0.483333,0.00100949,1646.12 +15,10,0.5,0.00112847,1782.26 +15,10,0,0,1604.81 +15,10,0.0166667,4.74519e-05,1475.07 +15,10,0.0333333,7.62899e-05,1743.11 +15,10,0.05,0.000114898,1496.36 +15,10,0.0666667,0.000191004,1872.87 +15,10,0.0833333,0.000186291,1460.16 +15,10,0.1,0.000272368,1529.41 +15,10,0.116667,0.000257438,1948.37 +15,10,0.133333,0.000278386,1777.98 +15,10,0.15,0.000384879,2089.9 +15,10,0.166667,0.000657542,1263.83 +15,10,0.183333,0.000431676,1839.03 +15,10,0.2,0.000440242,1749.11 +15,10,0.216667,0.000438821,1358.56 +15,10,0.233333,0.000479131,1306.58 +15,10,0.25,0.000501155,1585.41 +15,10,0.266667,0.000988177,1781.35 +15,10,0.283333,0.000481194,1481.99 +15,10,0.3,0.000570428,1773.74 +15,10,0.316667,0.00101012,1722.04 +15,10,0.333333,0.00115831,1735.79 +15,10,0.35,0.00121121,1618.25 +15,10,0.366667,0.000968161,1531.47 +15,10,0.383333,0.000855246,1777.2 +15,10,0.4,0.000808674,1618.37 +15,10,0.416667,0.000993498,1791.25 +15,10,0.433333,0.000982381,1241.2 +15,10,0.45,0.0010678,1832.68 +15,10,0.466667,0.00104481,1726.9 +15,10,0.483333,0.001036,2039.48 +15,10,0.5,0.00109417,1398.66 +15,10,0,0,1901.48 +15,10,0.0166667,4.45912e-05,2376.93 +15,10,0.0333333,7.78556e-05,1351.19 +15,10,0.05,0.000106275,1434.89 +15,10,0.0666667,0.000240648,1637.79 +15,10,0.0833333,0.000162048,1528.04 +15,10,0.1,0.000245335,1940.78 +15,10,0.116667,0.000275848,2145.94 +15,10,0.133333,0.000384719,1766.55 +15,10,0.15,0.000583623,1493.69 +15,10,0.166667,0.000510396,1443.36 +15,10,0.183333,0.000531731,1629.55 +15,10,0.2,0.000511027,2083.12 +15,10,0.216667,0.000465904,1651.28 +15,10,0.233333,0.000481363,1582.74 +15,10,0.25,0.00105095,2062.56 +15,10,0.266667,0.000975775,1370.16 +15,10,0.283333,0.00075319,1886.69 +15,10,0.3,0.000701083,1906.22 +15,10,0.316667,0.000725852,1532.9 +15,10,0.333333,0.000740865,1955.42 +15,10,0.35,0.000941612,1697.18 +15,10,0.366667,0.000677379,1818.5 +15,10,0.383333,0.000965704,1570.51 +15,10,0.4,0.000906912,1873.4 +15,10,0.416667,0.00103139,1428.5 +15,10,0.433333,0.00194035,1921.46 +15,10,0.45,0.00101062,1400 +15,10,0.466667,0.00124138,1919.03 +15,10,0.483333,0.0010501,1662.62 +15,10,0.5,0.00152565,1882.29 +15,10,0,0,1707.25 +15,10,0.0166667,3.606e-05,1218.93 +15,10,0.0333333,0.0001014,1966.26 +15,10,0.05,0.00010913,1692.63 +15,10,0.0666667,0.000174642,1855.91 +15,10,0.0833333,0.000237459,1872.15 +15,10,0.1,0.000196214,1648.32 +15,10,0.116667,0.000309684,2097.72 +15,10,0.133333,0.000485224,1531.85 +15,10,0.15,0.000277089,1380.62 +15,10,0.166667,0.000464924,1639.08 +15,10,0.183333,0.000494358,1668.97 +15,10,0.2,0.000644139,1427.77 +15,10,0.216667,0.000459207,1562.47 +15,10,0.233333,0.000515574,1871.26 +15,10,0.25,0.000728211,1903.25 +15,10,0.266667,0.00057068,1534.7 +15,10,0.283333,0.000641045,1489.2 +15,10,0.3,0.000827386,1286.55 +15,10,0.316667,0.00318544,1789.5 +15,10,0.333333,0.000742907,1962.4 +15,10,0.35,0.0010323,1840.76 +15,10,0.366667,0.000882751,1602.93 +15,10,0.383333,0.000890792,1987.27 +15,10,0.4,0.00163953,1847.08 +15,10,0.416667,0.00104985,1551.16 +15,10,0.433333,0.00145271,1732.59 +15,10,0.45,0.00122442,1896.22 +15,10,0.466667,0.00122484,1613.85 +15,10,0.483333,0.00151653,2049.31 +15,10,0.5,0.00166489,2100.18 diff --git a/src/calibrate/calibrate.cpp b/src/calibrate/calibrate.cpp index f4ace2fe..c4b83f33 100644 --- a/src/calibrate/calibrate.cpp +++ b/src/calibrate/calibrate.cpp @@ -7,7 +7,7 @@ namespace found { Quaternion LOSTCalibrationAlgorithm::Run(const std::pair &orientations) { - return SphericalToQuaternion(orientations.first) * SphericalToQuaternion(orientations.second).Conjugate(); + return SphericalToQuaternion(orientations.first) * SphericalToQuaternion(orientations.second).conjugate(); } } // namespace found diff --git a/src/command-line/execution/executors.cpp b/src/command-line/execution/executors.cpp index ef05bef8..1121346c 100644 --- a/src/command-line/execution/executors.cpp +++ b/src/command-line/execution/executors.cpp @@ -1,8 +1,8 @@ #include "command-line/execution/executors.hpp" +#include #include #include -#include #include "common/logging.hpp" #include "common/time/time.hpp" @@ -26,10 +26,10 @@ void CalibrationPipelineExecutor::ExecutePipeline() { void CalibrationPipelineExecutor::OutputResults() { // Output the results of the calibration Quaternion *&calibrationQuaternion = this->pipeline_.GetProduct(); - LOG_INFO("Calibration Quaternion: (" << calibrationQuaternion->real << ", " - << calibrationQuaternion->i << ", " - << calibrationQuaternion->j << ", " - << calibrationQuaternion->k << ")"); + LOG_INFO("Calibration Quaternion: (" << calibrationQuaternion->w() << ", " + << calibrationQuaternion->x() << ", " + << calibrationQuaternion->y() << ", " + << calibrationQuaternion->z() << ")"); DataFile outputDF{}; outputDF.relative_attitude = *calibrationQuaternion; std::ofstream outputFile(this->options_.outputFile); @@ -61,10 +61,10 @@ void DistancePipelineExecutor::ExecutePipeline() { void DistancePipelineExecutor::OutputResults() { PositionVector *&positionVector = this->pipeline_.GetProduct(); - LOG_INFO("Calculated Position: (" << positionVector->x << ", " - << positionVector->y << ", " - << positionVector->z << ") m"); - LOG_INFO("Distance from Earth: " << positionVector->Magnitude() << " m"); + LOG_INFO("Calculated Position: (" << positionVector->x() << ", " + << positionVector->y() << ", " + << positionVector->z() << ") m"); + LOG_INFO("Distance from Earth: " << positionVector->norm() << " m"); // TODO: Figure out a much more optimized way of doing this please, especially // since we're saving it into the exact same file, there should be an easy way // to simply modify the file directly instead of this mess. @@ -73,9 +73,9 @@ void DistancePipelineExecutor::OutputResults() { outputDF.header = this->options_.calibrationData.header; outputDF.relative_attitude = this->options_.calibrationData.relative_attitude; outputDF.positions = std::make_unique(outputDF.header.num_positions + 1); - std::memcpy(outputDF.positions.get(), - this->options_.calibrationData.positions.get(), - outputDF.header.num_positions); + std::copy(this->options_.calibrationData.positions.get(), + this->options_.calibrationData.positions.get() + outputDF.header.num_positions, + outputDF.positions.get()); } else { outputDF.relative_attitude = SphericalToQuaternion(this->options_.relOrientation); outputDF.positions = std::make_unique(1); @@ -106,9 +106,9 @@ void OrbitPipelineExecutor::ExecutePipeline() { void OrbitPipelineExecutor::OutputResults() { // TODO: Output this somewhere [[maybe_unused]] LocationRecord &futurePosition = this->pipeline_.GetProduct()->back(); - LOG_INFO("Calculated Future Position: (" << futurePosition.position.x << ", " - << futurePosition.position.y << ", " - << futurePosition.position.z << ") m" + LOG_INFO("Calculated Future Position: (" << futurePosition.position.x() << ", " + << futurePosition.position.y() << ", " + << futurePosition.position.z() << ") m" << " at time " << futurePosition.timestamp << " s"); } diff --git a/src/common/spatial/attitude-utils.cpp b/src/common/spatial/attitude-utils.cpp index 8f536443..2536482f 100644 --- a/src/common/spatial/attitude-utils.cpp +++ b/src/common/spatial/attitude-utils.cpp @@ -3,421 +3,79 @@ #include #include - -#include +#include "common/logging.hpp" namespace found { -/////////////////////////////////// -///////// VECTOR CLASSES ////////// -/////////////////////////////////// - -decimal Vec2::Magnitude() const { - return sqrt(MagnitudeSq()); -} - -decimal Vec2::MagnitudeSq() const { - return x*x+y*y; -} - -Vec2 Vec2::Normalize() const { - decimal mag = Magnitude(); - return { - x/mag, y/mag, - }; -} - -decimal Vec2::operator*(const Vec2 &other) const { - return x*other.x + y*other.y; -} - -Vec2 Vec2::operator*(const decimal &scalar) const { - return { x*scalar, y*scalar }; -} - -Vec2 Vec2::operator+(const Vec2 &other) const { - return {x + other.x, y + other.y }; -} - -Vec2 Vec2::operator-(const Vec2 &other) const { - return { x - other.x, y - other.y }; -} - -decimal Vec3::Magnitude() const { - return sqrt(MagnitudeSq()); -} - -decimal Vec3::MagnitudeSq() const { - return x*x+y*y+z*z; -} - -Vec3 Vec3::Normalize() const { - decimal mag = Magnitude(); - return { - x/mag, y/mag, z/mag, - }; -} - -Vec3 Vec3::operator-() const { - return { -x, -y, -z }; -} - -Vec3 Vec3::operator-(const Vec3 &other) const { - return { x - other.x, y - other.y, z - other.z }; -} - - -decimal Vec3::operator*(const Vec3 &other) const { - return x*other.x + y*other.y + z*other.z; -} - -Vec3 Vec3::operator*(const decimal &scalar) const { - return { x*scalar, y*scalar, z*scalar }; -} - -Vec3 Vec3::operator/(const decimal &divisor) const { - return { x / divisor, y / divisor, z / divisor }; -} - -Vec3 &Vec3::operator+=(const Vec3 &other) { - this->x += other.x; - this->y += other.y; - this->z += other.z; - - return *this; -} - -Vec3 Vec3::operator*(const Mat3 &other) const { - return { - x*other.At(0,0) + y*other.At(0,1) + z*other.At(0,2), - x*other.At(1,0) + y*other.At(1,1) + z*other.At(1,2), - x*other.At(2,0) + y*other.At(2,1) + z*other.At(2,2), - }; -} - -Vec3 Vec3::CrossProduct(const Vec3 &other) const { - return { - y*other.z - z*other.y, - -(x*other.z - z*other.x), - x*other.y - y*other.x, - }; -} - -Mat3 Vec3::OuterProduct(const Vec3 &other) const { - return { - x*other.x, x*other.y, x*other.z, - y*other.x, y*other.y, y*other.z, - z*other.x, z*other.y, z*other.z - }; -} - /////////////////////////////////// ///// VECTOR UTILITY FUNCTIONS //// /////////////////////////////////// Vec2 Midpoint(const Vec2 &vec1, const Vec2 &vec2) { - return {(vec1.x + vec2.x)/2, (vec1.y + vec2.y)/2}; + return (vec1 + vec2) / DECIMAL(2.0); } Vec3 Midpoint(const Vec3 &vec1, const Vec3 &vec2) { - return {(vec1.x + vec2.x)/2, (vec1.y + vec2.y)/2, (vec1.z + vec2.z)/2}; + return (vec1 + vec2) / DECIMAL(2.0); } Vec3 Midpoint(const Vec3 &vec1, const Vec3 &vec2, const Vec3 &vec3) { - return {(vec1.x + vec2.x + vec3.x)/3, (vec1.y + vec2.y + vec3.y)/3, (vec1.z + vec2.z + vec3.z)/3}; + return (vec1 + vec2 + vec3) / DECIMAL(3.0); } decimal Distance(const Vec2 &v1, const Vec2 &v2) { - return (v1-v2).Magnitude(); + return (v1 - v2).norm(); } decimal Distance(const Vec3 &v1, const Vec3 &v2) { - return (v1-v2).Magnitude(); + return (v1 - v2).norm(); } -decimal Angle(const Vec3 &vec1, const Vec3 &vec2) { - return AngleUnit(vec1.Normalize(), vec2.Normalize()); -} +// decimal Angle(const Vec3 &vec1, const Vec3 &vec2) { +// return AngleUnit(vec1.normalized(), vec2.normalized()); +// } -decimal AngleUnit(const Vec3 &vec1, const Vec3 &vec2) { - decimal dot = vec1*vec2; - // TODO: we shouldn't need this nonsense, right? how come acos sometimes gives nan? - return dot >= 1 ? 0 : dot <= -1 ? DECIMAL_M_PI-DECIMAL(0.0000001) : DECIMAL_ACOS(dot); -} +// decimal AngleUnit(const Vec3 &vec1, const Vec3 &vec2) { +// decimal dot = vec1.dot(vec2); +// // TODO: we shouldn't need this nonsense, right? how come acos sometimes gives nan? +// return dot >= 1 ? 0 : dot <= -1 ? DECIMAL_M_PI-DECIMAL(0.0000001) : DECIMAL_ACOS(dot); +// } /////////////////////////////////// -///////// MATRIX CLASS //////////// -/////////////////////////////////// - -decimal Mat3::At(int i, int j) const { - return x[3*i+j]; -} - -Vec3 Mat3::Column(int j) const { - return {At(0,j), At(1,j), At(2,j)}; -} - -Vec3 Mat3::Row(int i) const { - return {At(i,0), At(i,1), At(i,2)}; -} - -decimal Mat3::Trace() const { - return At(0,0) + At(1,1) + At(2,2); -} - -decimal Mat3::Det() const { - return (At(0,0) * (At(1,1)*At(2,2) - At(2,1)*At(1,2))) - - (At(0,1) * (At(1,0)*At(2,2) - At(2,0)*At(1,2))) + - (At(0,2) * (At(1,0)*At(2,1) - At(2,0)*At(1,1))); -} - -Mat3 Mat3::operator+(const Mat3 &other) const { - return { - At(0,0)+other.At(0,0), At(0,1)+other.At(0,1), At(0,2)+other.At(0,2), - At(1,0)+other.At(1,0), At(1,1)+other.At(1,1), At(1,2)+other.At(1,2), - At(2,0)+other.At(2,0), At(2,1)+other.At(2,1), At(2,2)+other.At(2,2) - }; -} - -Mat3 Mat3::operator*(const Mat3 &other) const { -#define _MATMUL_ENTRY(row, col) At(row,0)*other.At(0,col) + At(row,1)*other.At(1,col) + At(row,2)*other.At(2,col) - return { - _MATMUL_ENTRY(0,0), _MATMUL_ENTRY(0,1), _MATMUL_ENTRY(0,2), - _MATMUL_ENTRY(1,0), _MATMUL_ENTRY(1,1), _MATMUL_ENTRY(1,2), - _MATMUL_ENTRY(2,0), _MATMUL_ENTRY(2,1), _MATMUL_ENTRY(2,2), - }; -#undef _MATMUL_ENTRY -} - -Vec3 Mat3::operator*(const Vec3 &vec) const { - return { - vec.x*At(0,0) + vec.y*At(0,1) + vec.z*At(0,2), - vec.x*At(1,0) + vec.y*At(1,1) + vec.z*At(1,2), - vec.x*At(2,0) + vec.y*At(2,1) + vec.z*At(2,2), - }; -} - -Mat3 Mat3::operator*(const decimal &scalar) const { - return { - scalar*At(0,0), scalar*At(0,1), scalar*At(0,2), - scalar*At(1,0), scalar*At(1,1), scalar*At(1,2), - scalar*At(2,0), scalar*At(2,1), scalar*At(2,2) - }; -} - -Mat3 Mat3::Transpose() const { - return { - At(0,0), At(1,0), At(2,0), - At(0,1), At(1,1), At(2,1), - At(0,2), At(1,2), At(2,2), - }; -} - -Mat3 Mat3::Inverse() const { - // https://ardoris.wordpress.com/2008/07/18/general-formula-for-the-inverse-of-a-3x3-matrix/ - decimal scalar = 1 / Det(); - - Mat3 res = { - At(1,1)*At(2,2) - At(1,2)*At(2,1), At(0,2)*At(2,1) - At(0,1)*At(2,2), At(0,1)*At(1,2) - At(0,2)*At(1,1), - At(1,2)*At(2,0) - At(1,0)*At(2,2), At(0,0)*At(2,2) - At(0,2)*At(2,0), At(0,2)*At(1,0) - At(0,0)*At(1,2), - At(1,0)*At(2,1) - At(1,1)*At(2,0), At(0,1)*At(2,0) - At(0,0)*At(2,1), At(0,0)*At(1,1) - At(0,1)*At(1,0) - }; - - return res * scalar; -} - -/// 3x3 identity matrix -const Mat3 kIdentityMat3 = {1,0,0, - 0,1,0, - 0,0,1}; - -/////////////////////////////////// -///////// QUATERNION CLASS //////// +///////// QUATERNION HELPERS ////// /////////////////////////////////// /** - * Create a quaternion which represents a rotation of theta around the axis input - * - * @param input the axis about which theta is defined - * @param theta the rotation about the input -*/ -Quaternion::Quaternion(const Vec3 &input, decimal theta) { - real = cos(theta/2); - // the compiler will optimize it. Right? - i = input.x * sin(theta/2); - j = input.y * sin(theta/2); - k = input.z * sin(theta/2); -} - -Quaternion::Quaternion(const Vec3 &input) { - real = 0; - SetVector(input); -} - -Quaternion Quaternion::operator*(const Quaternion &other) const { - return Quaternion( - real*other.real - i*other.i - j*other.j - k*other.k, - real*other.i + other.real*i + j*other.k - k*other.j, - real*other.j + other.real*j + k*other.i - i*other.k, - real*other.k + other.real*k + i*other.j - j*other.i); -} - -Quaternion Quaternion::operator-() const { - return Quaternion(-real, -i, -j, -k); -} - -Quaternion Quaternion::Conjugate() const { - return Quaternion(real, -i, -j, -k); -} - -Vec3 Quaternion::Vector() const { - return { i, j, k }; -} - -void Quaternion::SetVector(const Vec3 &vec) { - i = vec.x; - j = vec.y; - k = vec.z; -} - -Vec3 Quaternion::Rotate(const Vec3 &input) const { - // TODO: optimize - return ((*this)*Quaternion(input)*Conjugate()).Vector(); -} - -decimal Quaternion::Angle() const { - if (real <= -1) { - return 0; // 180*2=360=0 - } - // TODO: we shouldn't need this nonsense, right? how come acos sometimes gives nan? (same as in AngleUnit) - return (real >= 1 ? 0 : acos(real))*2; -} - -void Quaternion::SetAngle(decimal newAngle) { - real = cos(newAngle/2); - SetVector(Vector().Normalize() * sin(newAngle/2)); -} - -bool Quaternion::IsUnit(decimal tolerance) const { - return abs(i*i+j*j+k*k+real*real - 1) < tolerance; -} - -Quaternion Quaternion::Canonicalize() const { - if (real >= 0) { - return *this; - } - - return Quaternion(-real, -i, -j, -k); -} - -EulerAngles Quaternion::ToSpherical() const { - // Working out these equations would be a pain in the ass. Thankfully, this wikipedia page: - // https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_angles_conversion - // uses almost exactly the same euler angle scheme that we do, so we copy their equations almost - // wholesale. The only differences are that 1, we use de and roll in the opposite directions, - // and 2, we store the conjugate of the quaternion (double check why?), which means we need to - // invert the final de and roll terms, as well as negate all the terms involving a mix between - // the real and imaginary parts. - decimal ra = atan2(2*(-real*k+i*j), 1-2*(j*j+k*k)); + * Converts a Quaternion to Euler Angles. + * + * Uses the z-y'-x'' convention matching the custom ToSpherical() implementation. + */ +EulerAngles QuaternionToSpherical(const Quaternion &q) { + // Eigen stores (x, y, z, w) internally but accessors are .w(), .x(), .y(), .z() + // Our convention: real=w, i=x, j=y, k=z + decimal w = q.w(), x = q.x(), y = q.y(), z = q.z(); + + decimal ra = atan2(2*(-w*z+x*y), 1-2*(y*y+z*z)); if (ra < 0) ra += 2*DECIMAL_M_PI; - decimal de = -asin(2*(-real*j-i*k)); // allow de to be positive or negaive, as is convention - decimal roll = -atan2(2*(-real*i+j*k), 1-2*(i*i+j*j)); + decimal de = -asin(2*(-w*y-x*z)); + decimal roll = -atan2(2*(-w*x+y*z), 1-2*(x*x+y*y)); if (roll < 0) roll += 2*DECIMAL_M_PI; return EulerAngles(ra, de, roll); } -/////////////////////////////////// -///////// ATTITUDE CLASS ////////// -/////////////////////////////////// - -Quaternion Attitude::GetQuaternion() const { - switch (type) { - case QuaternionType: - return quaternion; - case DCMType: - return DCMToQuaternion(dcm); - default: - throw std::runtime_error("No representation available"); - } -} - -Mat3 Attitude::GetDCM() const { - switch (type) { - case DCMType: - return dcm; - case QuaternionType: - return QuaternionToDCM(quaternion); - default: - throw std::runtime_error("No representation available"); - } -} - -EulerAngles Attitude::ToSpherical() const { - switch (type) { - case DCMType: - return GetQuaternion().ToSpherical(); - case QuaternionType: - return quaternion.ToSpherical(); - default: - throw std::runtime_error("No representation available"); - } -} - -Vec3 Attitude::Rotate(const Vec3 &vec) const { - switch (type) { - case DCMType: - return dcm*vec; - case QuaternionType: - return quaternion.Rotate(vec); - default: - throw std::runtime_error("No representation available"); - } -} - /////////////////////////////////// ////// CONVERSION FUNCTIONS /////// /////////////////////////////////// Mat3 QuaternionToDCM(const Quaternion &quat) { - Vec3 x = quat.Rotate({1, 0, 0}); - Vec3 y = quat.Rotate({0, 1, 0}); - Vec3 z = quat.Rotate({0, 0, 1}); - return { - x.x, y.x, z.x, - x.y, y.y, z.y, - x.z, y.z, z.z, - }; + return quat.toRotationMatrix(); } Quaternion DCMToQuaternion(const Mat3 &dcm) { - // Make a quaternion that rotates the reference frame X-axis into the dcm's X-axis, just like - // the DCM itself does - Vec3 oldXAxis = Vec3({1, 0, 0}); - Vec3 newXAxis = dcm.Column(0); // this is where oldXAxis is mapped to - assert(DECIMAL_ABS(newXAxis.Magnitude()-1) < DECIMAL(0.001)); - Vec3 xAlignAxis = oldXAxis.CrossProduct(newXAxis).Normalize(); - decimal xAlignAngle = AngleUnit(oldXAxis, newXAxis); - Quaternion xAlign(xAlignAxis, xAlignAngle); - - // Make a quaternion that will rotate the Y-axis into place - Vec3 oldYAxis = xAlign.Rotate({0, 1, 0}); - Vec3 newYAxis = dcm.Column(1); - // we still need to take the cross product, because acos returns a value in [0,pi], and thus we - // need to know which direction to rotate before we rotate. We do this by checking if the cross - // product of old and new y axes is in the same direction as the new X axis. - bool rotateClockwise = oldYAxis.CrossProduct(newYAxis) * newXAxis > 0; // * is dot product - Quaternion yAlign({1, 0, 0}, AngleUnit(oldYAxis, newYAxis) * (rotateClockwise ? 1 : -1)); - - // We're done! There's no need to worry about the Z-axis because the handed-ness of the - // coordinate system is always preserved, which means the Z-axis is uniquely determined as the - // cross product of the X- and Y-axes. This goes to show that DCMs store redundant information. - - // we want the y alignment to have memory of X, which means we put its multiplication on the - // right - return xAlign*yAlign; + return Quaternion(dcm); } Quaternion SphericalToQuaternion(decimal ra, decimal dec, decimal roll) { @@ -431,27 +89,44 @@ Quaternion SphericalToQuaternion(decimal ra, decimal dec, decimal roll) { // It is indeed correct that a positive rotation in our right-handed coordinate frame is in the // clockwise direction when looking down/through the axis of rotation. Just like the right hand // rule for magnetic field around a current-carrying conductor. - Quaternion a = Quaternion({ 0, 0, 1 }, ra); - Quaternion b = Quaternion({ 0, 1, 0 }, -dec); - Quaternion c = Quaternion({ 1, 0, 0 }, -roll); - Quaternion result = (a*b*c).Conjugate(); - assert(result.IsUnit(0.00001)); + Quaternion a(AngleAxis(ra, Vec3(0, 0, 1))); + Quaternion b(AngleAxis(-dec, Vec3(0, 1, 0))); + Quaternion c(AngleAxis(-roll, Vec3(1, 0, 0))); + Quaternion result = (a*b*c).conjugate(); + assert(DECIMAL_ABS(result.squaredNorm() - 1) < DECIMAL(0.00001)); return result; } Vec3 SphericalToSpatial(const decimal ra, const decimal de) { - return { - cos(ra)*cos(de), - sin(ra)*cos(de), - sin(de), - }; + return Vec3(cos(ra)*cos(de), + sin(ra)*cos(de), + sin(de)); } void SpatialToSpherical(const Vec3 &vec, decimal &ra, decimal &de) { - ra = atan2(vec.y, vec.x); + ra = atan2(vec.y(), vec.x()); if (ra < 0) ra += DECIMAL_M_PI*2; - de = asin(vec.z); + de = asin(vec.z()); +} + +Eigen::VectorXd TLS(Eigen::MatrixXd data) { + assert(data.cols() > 2 && "TLS input must have at least 3 columns"); + + // Since the input matrix should be thin and tall, the last column of V transpose + // will correspond to the vector with the smallest corresponding value in S, + // meaning it is the closest vector to the null space of the input + // We leave out Eigen::ComputeFullU so U will not be computed + Eigen::JacobiSVD svd(data, Eigen::ComputeFullV); + Eigen::MatrixXd VT = svd.matrixV(); + Eigen::VectorXd finalCol = VT.col(VT.cols()-1); + + // We do head(rows - 1) because the function asks for the number of coefficients in the vector + // not the index. Since we're just looking for a vector in the null space, it can have + // arbitrary scaling. We therefore divide by the final entry (and negate) so that + // the result dots to the outputs as we expect. + // note we do *1/[] rather than /[] because division isn't defined for the vector class + return finalCol.head(VT.rows()-1) * -(1/(finalCol(VT.rows()-1))); } } // namespace found diff --git a/src/common/spatial/attitude-utils.hpp b/src/common/spatial/attitude-utils.hpp index 32f21b08..392934d6 100644 --- a/src/common/spatial/attitude-utils.hpp +++ b/src/common/spatial/attitude-utils.hpp @@ -1,6 +1,9 @@ #ifndef SRC_COMMON_SPATIAL_ATTITUDE_UTILS_HPP_ #define SRC_COMMON_SPATIAL_ATTITUDE_UTILS_HPP_ +#include +#include + #include #include "common/decimal.hpp" @@ -16,217 +19,22 @@ namespace found { /////////////////////////////////// /** - * A Vec2 is an immutable object that represents a 2D Vector - * -*/ -struct Vec2 { - /// The x coordinate - decimal x; - /// The y coordinate - decimal y; - - // Magnitude - - /** - * Provides the magnitude of this Vec2 - * - * @return The magnitude of this - */ - decimal Magnitude() const; - - /** - * Provides the square of the magnitude of this Vec2 - * - * @return The square of the magnitude of this - */ - decimal MagnitudeSq() const; - - // Unit Vector - - /** - * Normalizes this - * - * @return The normalized vector of this - */ - Vec2 Normalize() const; - - // Vector Operations - - /** - * Dot product (Scalar product) - * - * @param other The other vector - * - * @return The dot of this and other - */ - decimal operator*(const Vec2 &other) const; - - /** - * Scalar-vector Product - * - * @param scalar The scalar - * - * @return scalar * this - */ - Vec2 operator*(const decimal &scalar) const; - - /** - * Vector Addition - * - * @param other The other vector - * - * @return this + other - */ - Vec2 operator+(const Vec2 &other) const; - - /** - * Vector Subtraction - * - * @param other The other vector - * - * @return this - other - */ - Vec2 operator-(const Vec2 &other) const; -}; - -class Mat3; // define above so we can use in Vec3 class + * A Vec2 is a 2D Vector + * + * @note Backed by Eigen::Matrix. + * Access components via .x(), .y(). Key methods: .norm(), .squaredNorm(), + * .normalized(), .dot(), +, -, * (scalar) + */ +using Vec2 = Eigen::Matrix; /** - * A Vec3 is a mutable object that represents a 3D Vector - * -*/ -class Vec3 { - public: - /// The x coordinate - decimal x; - /// The y coordinate - decimal y; - /// The z coordinate - decimal z; - - /** - * Construction of vector with x, y, and z components - * - * @param x The scalar value in the x direction of the vector to make - * @param y The scalar value in the y direction of the vector to make - * @param z The scalar value in the z direction of the vector to make - */ - constexpr Vec3(decimal x, decimal y, decimal z) : x(x), y(y), z(z) {} - - /** - * Default construction of the Vector - */ - Vec3() = default; - - // Magnitude - - /** - * Provides the magnitude of this Vec3 - * - * @return The magnitude of this - */ - decimal Magnitude() const; - - /** - * Provides the square of the magnitude of this Vec3 - * - * @return The square of the magnitude of this - */ - decimal MagnitudeSq() const; - - // Unit Vector - - /** - * Provides the magnitude of this Vec2 - * - * @return The magnitude of this - */ - Vec3 Normalize() const; - - // Operations - - /** - * Vector Negation - * - * @return -this - */ - Vec3 operator-() const; - - /** - * Vector Subtraction - * - * @param other The other vector - * - * @return this - other - */ - Vec3 operator-(const Vec3 &) const; - - /** - * Dot Product - * - * @param other The other vector - * - * @return The dot of this and other - */ - decimal operator*(const Vec3 &) const; - - /** - * Scalar Product - * - * @param scalar The scalar - * - * @return scalar * this - */ - Vec3 operator*(const decimal &) const; - - /** - * Scalar Division - * - * @param divisor The divisor - * - * @return this / divisor - */ - Vec3 operator/(const decimal &) const; - - /** - * Vector Addition (Modification) - * - * @param other The other vector - * - * @return this - * - * @post this has been added by other - */ - Vec3 &operator+=(const Vec3 &); - - /** - * Computes the product of a 3x3 matrix and a 3x1 vector (this) - * - * @param other The matrix to multiply this with - * - * @return other @ this - * - */ - Vec3 operator*(const Mat3 &) const; - - /** - * Computes the cross (vector) product between this and another vector - * - * @param other The vector to cross with this - * - * @return this X other - */ - Vec3 CrossProduct(const Vec3 &) const; - - /** - * Computes the outer product between this and another vector - * - * @param other The other vector in this operation - * - * @return this ⊗ other - */ - Mat3 OuterProduct(const Vec3 &) const; -}; + * A Vec3 is a 3D Vector + * + * @note Backed by Eigen::Matrix. + * Access components via .x(), .y(), .z(). Key methods: .norm(), .squaredNorm(), + * .normalized(), .dot(), .cross(), +, -, * (scalar), / (scalar) + */ +using Vec3 = Eigen::Matrix; /////////////////////////////////// ///// VECTOR UTILITY FUNCTIONS //// @@ -283,422 +91,52 @@ decimal Distance(const Vec2 &, const Vec2 &); */ decimal Distance(const Vec3 &, const Vec3 &); -/** - * Determines the angle between two different vectors - * - * @param vec1 The first vector - * @param vec2 The second vector - * - * @return The angle, in radians, between vec1 and vec2 -*/ -decimal Angle(const Vec3 &, const Vec3 &); +// decimal Angle(const Vec3 &, const Vec3 &); -/** - * Determines the angle between two different vectors - * - * @param vec1 The first vector - * @param vec2 The second vector - * - * @return The angle, in radians, between vec1 and vec2 - * - * @pre The magnitude of vec1 and vec2 are 1 -*/ -decimal AngleUnit(const Vec3 &, const Vec3 &); +// decimal AngleUnit(const Vec3 &, const Vec3 &); /////////////////////////////////// ///////// MATRIX CLASS //////////// /////////////////////////////////// /** - * A Mat3 is a mutable object that represents a 3x3 Matrix - * -*/ -class Mat3 { - public: - /// The matrix entries - decimal x[9]; - - // Accessor - - /** - * Obtains an entry in this Matrix - * - * @param i The row of the entry - * @param j The column of the entry - * - * @return The value of the entry in this at (i, j) - */ - decimal At(int i, int j) const; - - /** - * Obtains one of the column vectors in this Matrix - * - * @param j The column of the vector - * - * @return The vector at column j - */ - Vec3 Column(int) const; - - /** - * Obtains one of the row vectors in this Matrix - * - * @param i The row of the vector - * - * @return The vector at row i - */ - Vec3 Row(int) const; - - /** - * Obtains the trace of this Matrix - * - * @return The trace of this - */ - decimal Trace() const; - - /** - * Obtains the determinant of this Matrix - * - * @return The determinant of this - */ - decimal Det() const; - - // Operations - - /** - * Matrix Addition (element-wise) - * - * @param other - * - * @return this + other - */ - Mat3 operator+(const Mat3 &) const; - - /** - * Matrix Multiplication - * - * @param other The other matrix - * - * @return this @ other - */ - Mat3 operator*(const Mat3 &) const; - - /** - * Matrix-Vector Multiplication - * - * @param vec The vector to multiply - * - * @return this @ other - * - * @note Same as Vector::operator*(const Mat3 &), - * but with swapped parameters (duh?) - */ - Vec3 operator*(const Vec3 &) const; - - /** - * Matrix-Scalar Multiplication - * - * @param scalar The scalar to multiply with - * - * @return scalar * this - */ - Mat3 operator*(const decimal &) const; - - // Transformations - - /** - * Obtains the transpose of this Matrix - * - * @return The transpose Matrix of this - * - * @note Use this over Inverse if your - * matrix is orthogonal (e.g. A DCM) - */ - Mat3 Transpose() const; - - /** - * Obtains the inverse of this Matrix - * - * @return The inverse Matrix of this - */ - Mat3 Inverse() const; -}; - -/// Identity Matrix -extern const Mat3 kIdentityMat3; - -/////////////////////////////////// -//////// EULER ANGLES CLASS /////// -/////////////////////////////////// + * A Mat3 is a 3x3 Matrix + * + * @note Backed by Eigen::Matrix. + * Access entries via (i, j). Key methods: .col(j), .row(i), .trace(), + * .determinant(), .transpose(), .inverse(), +, * (Mat3, Vec3, scalar), + * Mat3::Identity() + */ +using Mat3 = Eigen::Matrix; /** - * An EulerAngle is a mutable Object representing Euler Angles of a 3D point - * - * A "human-readable" way to represent a 3d rotation or orientation. - * Euler angles roughly correspond to yaw, pitch, and roll of an airplane, - * which are easy * for humans to understand. - * - * There's no one single way to store Euler angles. We use z-y'-x'' angles, - * according to * the notation used on the wikipedia page for euler angles. + * EulerAngles represents a 3D orientation via right ascension, declination, and roll. + * + * @note Backed by Eigen::Matrix (Vec3). + * Components: .x() = right ascension (ra), .y() = declination (de), .z() = roll. + * We use z-y'-x'' Euler angles (yaw-pitch-roll order). */ -class EulerAngles { - public: - /** - * Initializes an EulerAngles with corresponding parameters - * - * @param ra The Right Ascension of the Euler Angle - * @param de The Declination of the Euler Angle - * @param roll The roll of the Euler Angle - */ - constexpr EulerAngles(decimal ra = DECIMAL(0.0), - decimal de = DECIMAL(0.0), - decimal roll = DECIMAL(0.0)) - : ra(ra), de(de), roll(roll) {} - - /// Right ascension. How far we yaw left. Yaw is performed first. - decimal ra; - /// Declination. How far we pitch up (or down if negative). Pitch is performed second, after yaw. - decimal de; - /// How far we roll counterclockwise. Roll is performed last (after yaw and pitch). - decimal roll; -}; +using EulerAngles = Vec3; /////////////////////////////////// ///////// QUATERNION CLASS //////// /////////////////////////////////// /** - * A Quaternion is a mutable object that represents a Quaternion. A Quaternion - * is a common way to represent rotations in 3D. - * -*/ -class Quaternion { - public: - Quaternion() = default; - - /** - * Create a quaternion which represents a rotation of theta around the axis input - * - * @param input the axis about which theta is defined - * @param theta the rotation about the input - */ - Quaternion(const Vec3 &, decimal); - - /** - * Creates a "pure quaternion" with the given vector for imaginary parts and zero for real part. - * - * @param input The vector representing the imaginary part of the Quaternion to create - * */ - explicit Quaternion(const Vec3 &); - - /** - * Creates a Quaternion with components - * - * @param real The real component - * @param i The i component - * @param j The j component - * @param k The k component - * - * Initializes this to be {real + iI + jJ + kK} - */ - Quaternion(decimal real, decimal i, decimal j, decimal k) - : real(real), i(i), j(j), k(k) {} - - /** - * Multiply two quaternions using the usual definition of - * quaternion multiplication (effectively composes rotations) - * - * @param other The other quaternion - * - * @return The resulting quaternion - */ - Quaternion operator*(const Quaternion &other) const; - - /** - * Negate the quaternion, which is the same rotation - * - * @return The resulting quaternion - */ - Quaternion operator-() const; - - /** - * Effectively computes a quaternion representing the inverse - * rotation of the original. - * - * @return The resulting quaternion - */ - Quaternion Conjugate() const; - - /** - * @brief The axis of the represented rotation. - * - * The vector formed by imaginary components of the quaternion. - * - * @return The vector part of this quaternion - */ - Vec3 Vector() const; - - /** - * Set imaginary components of Quarterion. - * - * @param vec The vector holding the imaginary quantities - * */ - void SetVector(const Vec3 &); - - /** - * Rotates a vector about this Quaternion - * - * @param input The vector to rotate about this - * - * @return The input vector that has been rotated about this - */ - Vec3 Rotate(const Vec3 &) const; - - /** - * Provides the amount of rotations represented by this Quaterion - * - * @return The amount of rotations represented by this, in radians - */ - decimal Angle() const; - - /** - * Changes the rotation represented in this quarterion - * - * @param newAngle The rotation angle this new quarternion should represent - * - * @post Changes this by altering the rotation angle of this by newAngle - */ - void SetAngle(decimal); - - /** - * Tells client if this Quarternion is a unit quaternion - * - * @param tolerance The tolerance for the magnitude of this - * - * @return true iff this is a unit Quaternion - * - */ - bool IsUnit(decimal tolerance) const; - - /** - * Provides the canonicalized Quaternion of this, if it is not already - * canonicalized - * - * @return The canonicalized Quaternion of this, which is this iff this - * is a canonicalized Quaternion already, and a new Quaternion if it is - * not - */ - Quaternion Canonicalize() const; - - /** - * Converts this Quarterion to a new system of Euler Angles by extracting the - * rotations described by this quarterion - * - * @return An EulerAngles object representing this in with Euler Angles - */ - EulerAngles ToSpherical() const; - - /// The real component - decimal real; - /// The i component - decimal i; - /// The j component - decimal j; - /// The k component - decimal k; -}; - -/////////////////////////////////// -///////// ATTITUDE CLASS ////////// -/////////////////////////////////// - -/** - * An Attitude is an immutable object that represents the orientation of a 3D point. - * Otherwise the attitude (orientation) of a spacecraft. - * - * @note This class wraps DCMs or Quaternions as the orientation - * - * @note The Attitude object stores either a rotation matrix (direction cosine matrix) - * or a quaternion, and converts automatically to the other format when needed. - * - * @note When porting to an embedded device, you'll probably want to get rid of this class and adapt to - * either quaternions or DCMs exclusively, depending on the natural output format of whatever - * attitude estimation algorithm you're using. + * A Quaternion represents a rotation in 3D space. + * + * @note Backed by Eigen::Quaternion. + * Access components via .w() (real), .x(), .y(), .z() (imaginary i, j, k). + * Key methods: .conjugate(), .inverse(), .norm(), .squaredNorm(), + * .normalized(), .toRotationMatrix(), .vec(), operator* (compose rotations), + * q * v (rotate vector v by quaternion q). + * Constructor: Quaternion(w, x, y, z) where w is the real/scalar part. + * Create from axis-angle: Quaternion(Eigen::AngleAxis(angle, axis)) */ -class Attitude { - public: - /** - * Constructs this by default - */ - Attitude() = default; - - /** - * Constructs an Attitude object from Quaternion information - * - * @param quat The quaternion to base the attitude off of - */ - explicit Attitude(const Quaternion &quat) : quaternion(quat), type(QuaternionType) {} - - /** - * Constructs an Attitude object from a Direction Cosine Matrix (A - * matrix holding the direction cosines for an attitude) - * - * @param matrix The matrix holding the direction cosines - */ - explicit Attitude(const Mat3 &matrix) : dcm(matrix), type(DCMType) {} - - /** - * Provides the Quaternion corresponding to this Attitude - * - * @return A Quaternion that holds the attitude information - * of this - */ - Quaternion GetQuaternion() const; - - /** - * Obtains the rotation matrix from this Attitude - * - * @return A matrix containing the direction cosines - * indicated by this - */ - Mat3 GetDCM() const; - - /** - * Obtains the Euler Angles of this Attitude - * - * @return An EulerAngles object that holds the Euler - * Angles of this - */ - EulerAngles ToSpherical() const; - - /** - * Obtains a vector rotated to this Attitude from another vector - * (Convert a vector from the reference frame to the body frame.) - * - * @param vec The vector to rotate - * - * @return A new vector that is rotated from vec based on this - */ - Vec3 Rotate(const Vec3 &) const; - - private: - /// Represents what internal representation for attitude - /// is being used - enum AttitudeType { - /// No Internal representation - NullType, - /// Quaternion Internal Representation - QuaternionType, - /// DCM Internal Representation - DCMType, - }; - - /// Internal Quaternion Representation - Quaternion quaternion; - /// Internal DCM Representation - Mat3 dcm; - /// Internal Representation Type - AttitudeType type; -}; +using Quaternion = Eigen::Quaternion; + +/// Convenience alias for Eigen::AngleAxis +using AngleAxis = Eigen::AngleAxis; /////////////////////////////////// ////// CONVERSION FUNCTIONS /////// @@ -731,6 +169,15 @@ Mat3 QuaternionToDCM(const Quaternion &); */ Quaternion DCMToQuaternion(const Mat3 &); +/** + * Converts a Quaternion to Euler Angles + * + * @param quat The quaternion to convert + * + * @return An EulerAngles representing the same orientation as the quaternion +*/ +EulerAngles QuaternionToSpherical(const Quaternion &quat); + /** * Converts Euler Angles into a quaternion * @@ -767,7 +214,7 @@ Quaternion SphericalToQuaternion(decimal ra, decimal dec, decimal roll); * Quaternions, DCMs, and Euler Angles. In this file, all 3 systems are backwards rotations. */ inline Quaternion SphericalToQuaternion(EulerAngles angles) - { return SphericalToQuaternion(angles.ra, angles.de, angles.roll); } + { return SphericalToQuaternion(angles.x(), angles.y(), angles.z()); } // Spherical-Vector Conversions @@ -851,6 +298,29 @@ constexpr decimal ArcSecToRad(decimal arcSec) { return DegToRad(arcSec / DECIMAL(3600.0)); } +/////////////////////////////////// +///// LEAST SQUARES FUNCTIONS ///// +/////////////////////////////////// + +/** + * Performs total least squares on the given matrix + * using SVD + * + * @param data An NxM matrix with each row corresponding to a vector and an expected dot product output + * + * i.e rows enumerated by j may contain [position[j].x, position[j].y, position[j].z, 1] + * and the algorithm will output a Vec3 that dots with each of the positions to 1 with as little + * error as possible + * + * @note the expected output must NOT be negated, as is convention in some implementations. + * I personally find it more intuitive this way, but if it's confusing people I can change it + * + * @return The vector of length (M-1) that minimizes the error + * Notice the vector is M-1 because it must dot with the vector formed by the M-1 entries of a row + * to get the result in the M'th entry +*/ +Eigen::VectorXd TLS(Eigen::MatrixXd data); + } // namespace found #endif // SRC_COMMON_SPATIAL_ATTITUDE_UTILS_HPP_ diff --git a/src/common/spatial/camera.cpp b/src/common/spatial/camera.cpp index f60d123f..2142470e 100644 --- a/src/common/spatial/camera.cpp +++ b/src/common/spatial/camera.cpp @@ -13,15 +13,15 @@ namespace found { Vec2 Camera::SpatialToCamera(const Vec3 &vector) const { // can't handle things behind the camera. - assert(vector.x > 0); + assert(vector.x() > 0); // TODO: is there any sort of accuracy problem when vector.y and vector.z are small? - decimal focalFactor = focalLength/vector.x/pixelSize; + decimal focalFactor = focalLength/vector.x()/pixelSize; - decimal yPixel = vector.y*focalFactor; - decimal zPixel = vector.z*focalFactor; + decimal yPixel = vector.y()*focalFactor; + decimal zPixel = vector.z()*focalFactor; - return { -yPixel + xCenter, -zPixel + yCenter }; + return Vec2(-yPixel + xCenter, -zPixel + yCenter); } Vec3 Camera::CameraToSpatial(const Vec2 &vector) const { @@ -29,22 +29,20 @@ Vec3 Camera::CameraToSpatial(const Vec2 &vector) const { // isn't it interesting: To convert from center-based to left-corner-based coordinates is the // same formula; f(x)=f^{-1}(x) ! - decimal xPixel = -vector.x + xCenter; - decimal yPixel = -vector.y + yCenter; - - return { - 1, - xPixel * pixelSize / focalLength, - yPixel * pixelSize / focalLength, - }; + decimal xPixel = -vector.x() + xCenter; + decimal yPixel = -vector.y() + yCenter; + + return Vec3(1, + xPixel * pixelSize / focalLength, + yPixel * pixelSize / focalLength); } bool Camera::InSensor(const Vec2 &vector) const { // if vector.x == xResolution, then it is at the leftmost point // of the pixel that's "hanging off" the edge of the image, // so vector is still in the image. - return vector.x >= 0 && vector.x <= xResolution - && vector.y >= 0 && vector.y <= yResolution; + return vector.x() >= 0 && vector.x() <= xResolution + && vector.y() >= 0 && vector.y() <= yResolution; } decimal Camera::Fov() const { diff --git a/src/datafile/serialization.cpp b/src/datafile/serialization.cpp index f1174d93..e57385f6 100644 --- a/src/datafile/serialization.cpp +++ b/src/datafile/serialization.cpp @@ -147,10 +147,10 @@ inline void read(std::istream& stream, uint32_t& value) { * */ inline void write(std::ostream& stream, const Quaternion& quat) { - write(stream, quat.real); - write(stream, quat.i); - write(stream, quat.j); - write(stream, quat.k); + write(stream, quat.w()); + write(stream, quat.x()); + write(stream, quat.y()); + write(stream, quat.z()); } /** @@ -164,10 +164,12 @@ inline void write(std::ostream& stream, const Quaternion& quat) { * */ inline void read(std::istream& stream, Quaternion& quat) { - read(stream, quat.real); - read(stream, quat.i); - read(stream, quat.j); - read(stream, quat.k); + decimal w, x, y, z; + read(stream, w); + read(stream, x); + read(stream, y); + read(stream, z); + quat = Quaternion(w, x, y, z); } /** @@ -181,9 +183,9 @@ inline void read(std::istream& stream, Quaternion& quat) { * */ inline void write(std::ostream& stream, const Vec3& v) { - write(stream, v.x); - write(stream, v.y); - write(stream, v.z); + write(stream, v.x()); + write(stream, v.y()); + write(stream, v.z()); } /** @@ -197,9 +199,11 @@ inline void write(std::ostream& stream, const Vec3& v) { * */ inline void read(std::istream& stream, Vec3& v) { - read(stream, v.x); - read(stream, v.y); - read(stream, v.z); + decimal vx, vy, vz; + read(stream, vx); + read(stream, vy); + read(stream, vz); + v = Vec3(vx, vy, vz); } /** diff --git a/src/distance/distance.cpp b/src/distance/distance.cpp index ff2da654..7bad468b 100644 --- a/src/distance/distance.cpp +++ b/src/distance/distance.cpp @@ -17,11 +17,11 @@ namespace found { ///// SphericalDistanceDeterminationAlgorithm ///// PositionVector SphericalDistanceDeterminationAlgorithm::Run(const Points &p) { - if (p.size() < 3) return {0, 0, 0}; + if (p.size() < 3) return Vec3(0, 0, 0); - const Vec3 spats[3] = {cam_.CameraToSpatial(p[0]).Normalize(), - cam_.CameraToSpatial(p[p.size() / 2]).Normalize(), - cam_.CameraToSpatial(p[p.size() - 1]).Normalize()}; + const Vec3 spats[3] = {cam_.CameraToSpatial(p[0]).normalized(), + cam_.CameraToSpatial(p[p.size() / 2]).normalized(), + cam_.CameraToSpatial(p[p.size() - 1]).normalized()}; return this->Run(spats[0], spats[1], spats[2]); } @@ -37,7 +37,7 @@ PositionVector SphericalDistanceDeterminationAlgorithm::Run(const Vec3 &a, const decimal h = this->radius_/this->r_; // You have to normalize the center vector here - return this->center_.Normalize() * h; + return this->center_.normalized() * h; } Vec3 SphericalDistanceDeterminationAlgorithm::getCenter(const Vec3 &a, const Vec3 &b, const Vec3 &c) { @@ -45,7 +45,7 @@ Vec3 SphericalDistanceDeterminationAlgorithm::getCenter(const Vec3 &a, const Vec Vec3 diff2 = c - b; // Cross product to find the normal vector for points on earth - Vec3 circleN = diff1.CrossProduct(diff2); + Vec3 circleN = diff1.cross(diff2); Vec3 circlePt = a; // Mid point between 2 vectors @@ -68,16 +68,17 @@ Vec3 SphericalDistanceDeterminationAlgorithm::getCenter(const Vec3 &a, const Vec * This becomes a systems of linear equation */ Mat3 matrix; - matrix = {circleN.x, circleN.y, circleN.z, mid1N.x, mid1N.y, - mid1N.z, mid2N.x, mid2N.y, mid2N.z}; + matrix << circleN.x(), circleN.y(), circleN.z(), + mid1N.x(), mid1N.y(), mid1N.z(), + mid2N.x(), mid2N.y(), mid2N.z(); - decimal alpha = circleN*circlePt; - decimal beta = mid1N*mid1; - decimal gamma = mid2N*mid2; + decimal alpha = circleN.dot(circlePt); + decimal beta = mid1N.dot(mid1); + decimal gamma = mid2N.dot(mid2); - Vec3 y = {alpha, beta, gamma}; + Vec3 y(alpha, beta, gamma); - Vec3 center = matrix.Inverse() * y; + Vec3 center = matrix.inverse() * y; return center; } @@ -111,7 +112,7 @@ IterativeSphericalDistanceDeterminationAlgorithm::IterativeSphericalDistanceDete PositionVector IterativeSphericalDistanceDeterminationAlgorithm::Run(const Points &p) { // Step -1: Return zero if the number of points is less than 0 - if (p.size() < 3) return {0, 0, 0}; + if (p.size() < 3) return Vec3(0, 0, 0); // Step 0: Determine the number of iterations size_t numIterations = this->minimumIterations_ > p.size() / 3 ? this->minimumIterations_ : p.size(); @@ -125,14 +126,14 @@ PositionVector IterativeSphericalDistanceDeterminationAlgorithm::Run(const Point size_t pointsSize = p.size(); std::unique_ptr projectedPoints(new Vec3[pointsSize]); for (const Vec2 &point : p) { - projectedPoints[i++] = this->cam_.CameraToSpatial(point).Normalize(); + projectedPoints[i++] = this->cam_.CameraToSpatial(point).normalized(); } i = 0; std::unique_ptr logits(new uint64_t[pointsSize]); // Step 2a: Use the first estimate as a reference PositionVector first(SphericalDistanceDeterminationAlgorithm::Run(p)); - decimal targetDistSq = first.MagnitudeSq(); + decimal targetDistSq = first.squaredNorm(); decimal referenceLoss = this->GenerateLoss(first, targetDistSq, projectedPoints, pointsSize); // Step 2b: Setup the cumulative loss and position. We are using softmax, normalized on the reference decimal totalLoss = DECIMAL_EXP(-1.0); @@ -155,7 +156,7 @@ PositionVector IterativeSphericalDistanceDeterminationAlgorithm::Run(const Point if (i % refreshFrequency == 0) { totalPosition = totalPosition / totalLoss; - targetDistSq = totalPosition.MagnitudeSq(); + targetDistSq = totalPosition.squaredNorm(); referenceLoss = this->GenerateLoss(totalPosition, targetDistSq, projectedPoints, pointsSize); totalLoss = DECIMAL_EXP(-1.0); totalPosition = totalPosition * totalLoss; @@ -174,14 +175,14 @@ decimal IterativeSphericalDistanceDeterminationAlgorithm::GenerateLoss(PositionV // error): decimal loss = DECIMAL(1e-3); // If the distance error is outside the ratio, then we add distance loss - decimal distance_sq_loss_ratio = DECIMAL_ABS((targetDistanceSq - position.MagnitudeSq())) / targetDistanceSq; + decimal distance_sq_loss_ratio = DECIMAL_ABS((targetDistanceSq - position.squaredNorm())) / targetDistanceSq; if (distance_sq_loss_ratio >= this->distanceRatioSq_) { loss += distance_sq_loss_ratio * targetDistanceSq; } // Now, we obtain the radius error decimal targetRadiusSq = this->r_ * this->r_; for (size_t k = 0; k < size; k++) { - decimal radius_loss_k = targetRadiusSq - (this->center_ - projectedPoints[k]).MagnitudeSq(); + decimal radius_loss_k = targetRadiusSq - (this->center_ - projectedPoints[k]).squaredNorm(); loss += DECIMAL_POW(radius_loss_k, this->radiusLossOrder_); } diff --git a/src/distance/edge.cpp b/src/distance/edge.cpp index d5068960..2b8e5ee1 100644 --- a/src/distance/edge.cpp +++ b/src/distance/edge.cpp @@ -31,10 +31,10 @@ Points SimpleEdgeDetectionAlgorithm::Run(const Image &image) { for (auto &component : spaces) { // Basically, if the component touches the border, and its the biggest one, // we assume it is space - if ((component.upperLeft.x < this->borderLength_ || - component.upperLeft.y < this->borderLength_ || - component.lowerRight.x >= image.width - this->borderLength_ || - component.lowerRight.y >= image.height - this->borderLength_)) { + if ((component.upperLeft.x() < this->borderLength_ || + component.upperLeft.y() < this->borderLength_ || + component.lowerRight.x() >= image.width - this->borderLength_ || + component.lowerRight.y() >= image.height - this->borderLength_)) { if (!space || component.points.size() > space->points.size()) space = &component; } @@ -45,61 +45,61 @@ Points SimpleEdgeDetectionAlgorithm::Run(const Image &image) { // Step 2: Identify the edge as the edge of space // Step 2a: Figure out the centroids of space and the planet - Vec2 planetCentroid{0, 0}; - Vec2 spaceCentroid{0, 0}; + Vec2 planetCentroid(0, 0); + Vec2 spaceCentroid(0, 0); int64_t planetSize = 0; int64_t spaceSize = 0; for (uint64_t i = 0; i < imageSize; i++) { if (points.find(i) == points.end()) { - planetCentroid.x += i % image.width; - planetCentroid.y += i / image.width; + planetCentroid.x() += i % image.width; + planetCentroid.y() += i / image.width; planetSize++; } else { - spaceCentroid.x += i % image.width; - spaceCentroid.y += i / image.width; + spaceCentroid.x() += i % image.width; + spaceCentroid.y() += i / image.width; spaceSize++; } } - planetCentroid.x /= planetSize; - planetCentroid.y /= planetSize; - spaceCentroid.x /= spaceSize; - spaceCentroid.y /= spaceSize; + planetCentroid.x() /= planetSize; + planetCentroid.y() /= planetSize; + spaceCentroid.x() /= spaceSize; + spaceCentroid.y() /= spaceSize; Vec2 itrDirection = spaceCentroid - planetCentroid; // Step 2b: Figure out how to iterate through the image, // iterating from the planet into space Points result; - if (std::abs(itrDirection.y) > std::abs(itrDirection.x)) { + if (std::abs(itrDirection.y()) > std::abs(itrDirection.x())) { // Determine which direction we want to iterate, // (we want to iterate into the space) uint64_t update; uint64_t start; decimal offset; uint64_t edge_condition; - if (itrDirection.y < 0) { + if (itrDirection.y() < 0) { // Iterate up, and start at the bottom left corner update = -image.width; - start = static_cast(space->lowerRight.y * image.width + space->upperLeft.x); + start = static_cast(space->lowerRight.y() * image.width + space->upperLeft.x()); offset = -this->offset_; edge_condition = image.height - 1; } else { // Iterate down, and start at the top left corner update = image.width; - start = static_cast(space->upperLeft.y * image.width + space->upperLeft.x); + start = static_cast(space->upperLeft.y() * image.width + space->upperLeft.x()); offset = this->offset_; edge_condition = 0; } // Step 2c: Get all edge points along the edge, identifying the edge // as the first point that is found inside space - for (int col = space->upperLeft.x; col <= space->lowerRight.x; col++) { + for (int col = space->upperLeft.x(); col <= space->lowerRight.x(); col++) { // because index is uint64_t, going below zero will overflow, and it will indeed be past the dimensions uint64_t index = start; while (points.find(index) == points.end()) index += update; if (index / image.width != edge_condition) { index -= update; - result.push_back({DECIMAL(index % image.width), - DECIMAL(index / image.width) - offset}); + result.push_back(Vec2(DECIMAL(index % image.width), + DECIMAL(index / image.width) - offset)); } start++; } @@ -109,28 +109,28 @@ Points SimpleEdgeDetectionAlgorithm::Run(const Image &image) { uint64_t start; decimal offset; uint64_t edge_condition; - if (itrDirection.x < 0) { + if (itrDirection.x() < 0) { // Iterate left, and start at the top right corner update = -1; - start = static_cast(space->upperLeft.y * image.width + space->lowerRight.x); + start = static_cast(space->upperLeft.y() * image.width + space->lowerRight.x()); offset = -this->offset_; edge_condition = image.width - 1; } else { // Iterate right, and start at the top left corner update = 1; - start = static_cast(space->upperLeft.y * image.width + space->upperLeft.x); + start = static_cast(space->upperLeft.y() * image.width + space->upperLeft.x()); offset = this->offset_; edge_condition = 0; } // Step 2c: Get all edge points along the edge, identifying the edge // as the first point that is found inside space - for (int row = space->upperLeft.y; row <= space->lowerRight.y; row++) { + for (int row = space->upperLeft.y(); row <= space->lowerRight.y(); row++) { uint64_t index = start; while (points.find(index) == points.end()) index += update; if (index % image.width != edge_condition) { index -= update; - result.push_back({DECIMAL(index % image.width) - offset, - DECIMAL(index / image.width)}); + result.push_back(Vec2(DECIMAL(index % image.width) - offset, + DECIMAL(index / image.width))); } start += image.width; } @@ -172,11 +172,11 @@ inline bool LabelPresent(int label, int *adjacentLabels, int size) { */ inline void UpdateComponent(Component &component, uint64_t index, Vec2 &pixel) { component.points.insert(index); - if (component.upperLeft.x > pixel.x) component.upperLeft.x = pixel.x; - else if (component.lowerRight.x < pixel.x) component.lowerRight.x = pixel.x; + if (component.upperLeft.x() > pixel.x()) component.upperLeft.x() = pixel.x(); + else if (component.lowerRight.x() < pixel.x()) component.lowerRight.x() = pixel.x(); // We skip this statement, since its impossible: - // if (component.upperLeft.y > pixel.y) component.upperLeft.y = pixel.y; - if (component.lowerRight.y < pixel.y) component.lowerRight.y = pixel.y; + // if (component.upperLeft.y() > pixel.y()) component.upperLeft.y() = pixel.y(); + if (component.lowerRight.y() < pixel.y()) component.lowerRight.y() = pixel.y(); } /** @@ -350,11 +350,16 @@ Components ConnectedComponentsAlgorithm(const Image &image, std::functionsecond; auto &lowestComp = components[lowestLabel]; lowestComp.points.insert(compToMerge.points.begin(), compToMerge.points.end()); - if (compToMerge.upperLeft.x < lowestComp.upperLeft.x) lowestComp.upperLeft.x = compToMerge.upperLeft.x; - if (compToMerge.lowerRight.x > lowestComp.lowerRight.x) lowestComp.lowerRight.x = compToMerge.lowerRight.x; - // We skip this statement, because its impossible (a higher component is level or lower than a lower component): - // if (compToMerge.upperLeft.y < lowestComp.upperLeft.y) lowestComp.upperLeft.y = compToMerge.upperLeft.y; - if (compToMerge.lowerRight.y > lowestComp.lowerRight.y) lowestComp.lowerRight.y = compToMerge.lowerRight.y; + if (compToMerge.upperLeft.x() < lowestComp.upperLeft.x()) + lowestComp.upperLeft.x() = compToMerge.upperLeft.x(); + if (compToMerge.lowerRight.x() > lowestComp.lowerRight.x()) + lowestComp.lowerRight.x() = compToMerge.lowerRight.x(); + // We skip this statement, because its impossible + // (a higher component is level or lower than a lower component): + // if (compToMerge.upperLeft.y() < lowestComp.upperLeft.y()) + // lowestComp.upperLeft.y() = compToMerge.upperLeft.y(); + if (compToMerge.lowerRight.y() > lowestComp.lowerRight.y()) + lowestComp.lowerRight.y() = compToMerge.lowerRight.y(); components.erase(compIt); } diff --git a/src/distance/output.cpp b/src/distance/output.cpp index a426041d..12129729 100644 --- a/src/distance/output.cpp +++ b/src/distance/output.cpp @@ -15,17 +15,18 @@ EarthSphericalVec3 GetEarthCoordinates(Vec3 &celestialVector, decimal gmst) { decimal GMST = std::fmod(DECIMAL_M_PI * gmst / DECIMAL(180.0), 2 * DECIMAL_M_PI); // Figure out Earth's Rotating Frame and express the position in that frame Quaternion toEarthRotatingFrame = SphericalToQuaternion(GMST, 0, 0); - Vec3 position = toEarthRotatingFrame.Rotate(celestialVector); + Vec3 position = toEarthRotatingFrame * celestialVector; // Figure out the right ascension and declination of the vector - decimal RA = std::atan2(position.y, position.x); // Huh, the range is [-PI, PI], not [0, 2PI]. That's convenient - decimal DE = std::asin(position.Normalize().z); // Range is [-PI/2, PI/2] + // Range is [-PI, PI], not [0, 2PI]. That's convenient + decimal RA = std::atan2(position.y(), position.x()); + decimal DE = std::asin(position.normalized().z()); // Range is [-PI/2, PI/2] // Longitude, Lattitude and Altitude Follow, with conversion // to degrees and range adjustment from RA to longitude return {RadToDeg(RA), RadToDeg(DE), - position.Magnitude(), + position.norm(), gmst}; } diff --git a/src/distance/vectorize.cpp b/src/distance/vectorize.cpp index 728769bb..ee5ef68e 100644 --- a/src/distance/vectorize.cpp +++ b/src/distance/vectorize.cpp @@ -8,7 +8,7 @@ PositionVector LOSTVectorGenerationAlgorithm::Run(const PositionVector &x_E) { // Use the conjugate here, since the orientation is a backwards rotation. In // other words, the orientation is a rotation from the celestial frame to the // camera frame, but we want to go the other way. - return -this->orientation.Conjugate().Rotate(x_E); + return -(this->orientation.conjugate() * x_E); } } // namespace found diff --git a/src/providers/converters.hpp b/src/providers/converters.hpp index f591ffe3..2d7a76a5 100644 --- a/src/providers/converters.hpp +++ b/src/providers/converters.hpp @@ -160,7 +160,7 @@ inline LocationRecords strtolr(const std::string &str) { while (std::getline(file, line)) { std::istringstream iss(line); LocationRecord record; - if (!(iss >> record.timestamp >> record.position.x >> record.position.y >> record.position.z)) { + if (!(iss >> record.timestamp >> record.position.x() >> record.position.y() >> record.position.z())) { file.close(); throw std::runtime_error("Invalid format for file " + str + ": " + line); } diff --git a/test/calibrate/calibrate-test.cpp b/test/calibrate/calibrate-test.cpp index 720cc9aa..25c43bb5 100644 --- a/test/calibrate/calibrate-test.cpp +++ b/test/calibrate/calibrate-test.cpp @@ -12,11 +12,13 @@ namespace found { TEST(CalibrationTest, TestCalibrateAbsolute) { - EulerAngles local = Quaternion(0.36, 0.48, 0.64, 0.48).ToSpherical(); + EulerAngles local = QuaternionToSpherical(Quaternion(0.36, 0.48, 0.64, 0.48)); EulerAngles reference(0, 0, 0); LOSTCalibrationAlgorithm algorithm; - EulerAngles actual = algorithm.Run(std::make_pair(local, reference)).Canonicalize().ToSpherical(); + Quaternion result = algorithm.Run(std::make_pair(local, reference)); + if (result.w() < 0) result = Quaternion(-result.w(), -result.x(), -result.y(), -result.z()); + EulerAngles actual = QuaternionToSpherical(result); ASSERT_EA_EQ_DEFAULT(local, actual); } @@ -29,7 +31,9 @@ TEST(CalibrationTest, TestCalibrateRelativeSimple1) { EulerAngles expected(7 * DECIMAL_M_PI / 4, 0, 0); LOSTCalibrationAlgorithm algorithm; - EulerAngles actual = algorithm.Run(std::make_pair(local, reference)).Canonicalize().ToSpherical(); + Quaternion result1 = algorithm.Run(std::make_pair(local, reference)); + if (result1.w() < 0) result1 = Quaternion(-result1.w(), -result1.x(), -result1.y(), -result1.z()); + EulerAngles actual = QuaternionToSpherical(result1); ASSERT_EA_EQ_DEFAULT(expected, actual); } @@ -41,9 +45,11 @@ TEST(CalibrationTest, TestCalibrateRelativeSimple2) { EulerAngles expected(0, DECIMAL_M_PI / 6, 0); LOSTCalibrationAlgorithm algorithm; - EulerAngles actual = algorithm.Run(std::make_pair(local, reference)).Canonicalize().ToSpherical(); + Quaternion result2 = algorithm.Run(std::make_pair(local, reference)); + if (result2.w() < 0) result2 = Quaternion(-result2.w(), -result2.x(), -result2.y(), -result2.z()); + EulerAngles actual2 = QuaternionToSpherical(result2); - ASSERT_EA_EQ_DEFAULT(expected, actual); + ASSERT_EA_EQ_DEFAULT(expected, actual2); } TEST(CalibrationTest, TestCalibrateGeneral) { @@ -64,8 +70,8 @@ TEST(CalibrationTest, TestCalibrateGeneral) { Quaternion diffLocal = DCMToQuaternion(QuaternionToDCM(result) * QuaternionToDCM(diffReference)); // See if the calibration holds for different axes - ASSERT_QUAT_EQ_DEFAULT((expectedLocalQ * SphericalToQuaternion(reference).Conjugate()), - (diffLocal * diffReference.Conjugate())); + ASSERT_QUAT_EQ_DEFAULT((expectedLocalQ * SphericalToQuaternion(reference).conjugate()), + (diffLocal * diffReference.conjugate())); } } // namespace found diff --git a/test/command-line/execution/executors-test.cpp b/test/command-line/execution/executors-test.cpp index cc2fc16e..5c44c3e2 100644 --- a/test/command-line/execution/executors-test.cpp +++ b/test/command-line/execution/executors-test.cpp @@ -38,12 +38,12 @@ MATCHER_P(ImageMatcher, expected, "") { MATCHER_P(PointsMatcher, expected, "") { return arg.size() == expected.size() && std::equal(arg.begin(), arg.end(), expected.begin(), expected.end(), [](const Vec2& a, const Vec2& b) { - return a.x == b.x && a.y == b.y; + return a.x() == b.x() && a.y() == b.y(); }); } MATCHER_P(PositionVectorMatcher, expected, "") { - return arg.x == expected.x && arg.y == expected.y && arg.z == expected.z; + return arg.x() == expected.x() && arg.y() == expected.y() && arg.z() == expected.z(); } TEST(ExecutorsTest, TestCalibrationPipelineExecutor) { diff --git a/test/command-line/parsing/parser-test.cpp b/test/command-line/parsing/parser-test.cpp index 6aad88cf..7041a7bd 100644 --- a/test/command-line/parsing/parser-test.cpp +++ b/test/command-line/parsing/parser-test.cpp @@ -20,13 +20,13 @@ TEST_F(ParserTest, CalibrationParserBaseCase) { const char *argv[] = {"found", "calibration"}; CalibrationOptions options = ParseCalibrationOptions(argc, const_cast(argv)); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.lclOrientation.ra); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.lclOrientation.de); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.lclOrientation.roll); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.lclOrientation.x()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.lclOrientation.y()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.lclOrientation.z()); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.refOrientation.ra); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.refOrientation.de); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.refOrientation.roll); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.refOrientation.x()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.refOrientation.y()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), options.refOrientation.z()); ASSERT_EQ("", options.outputFile); } @@ -39,13 +39,13 @@ TEST_F(ParserTest, TestCalibrationParserGeneral) { "--output-file", temp_df}; CalibrationOptions options = ParseCalibrationOptions(argc, const_cast(argv)); - ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(1), options.lclOrientation.ra); - ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(2), options.lclOrientation.de); - ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(3), options.lclOrientation.roll); + ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(1), options.lclOrientation.x()); + ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(2), options.lclOrientation.y()); + ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(3), options.lclOrientation.z()); - ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(3.0), options.refOrientation.ra); - ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(-9.0), options.refOrientation.de); - ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(27.2), options.refOrientation.roll); + ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(3.0), options.refOrientation.x()); + ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(-9.0), options.refOrientation.y()); + ASSERT_DECIMAL_EQ_DEFAULT(DegToRad(27.2), options.refOrientation.z()); ASSERT_EQ(temp_df, options.outputFile); } diff --git a/test/common-test/time/time-test.cpp b/test/common-test/time/time-test.cpp index 4d88b319..85c5d333 100644 --- a/test/common-test/time/time-test.cpp +++ b/test/common-test/time/time-test.cpp @@ -139,7 +139,7 @@ TEST(TimeTest, TestGetJulianDateNow) { decimal expectedJulianDate = time.epochs / 86400.0 + 2440587.5; #ifndef FOUND_FLOAT_MODE - ASSERT_RANGE(julianDate, expectedJulianDate, expectedJulianDate + SECONDS_TOLERANCE); + ASSERT_RANGE(julianDate, expectedJulianDate - SECONDS_TOLERANCE, expectedJulianDate + SECONDS_TOLERANCE); #else ASSERT_RANGE(julianDate, expectedJulianDate - SECONDS_TOLERANCE, expectedJulianDate + SECONDS_TOLERANCE); #endif diff --git a/test/common/common.hpp b/test/common/common.hpp index 448bcddb..d29a80de 100644 --- a/test/common/common.hpp +++ b/test/common/common.hpp @@ -14,17 +14,28 @@ namespace found { +///// Test-only helpers ///// + +inline decimal AngleUnit(const Vec3 &vec1, const Vec3 &vec2) { + decimal dot = vec1.dot(vec2); + return dot >= 1 ? 0 : dot <= -1 ? DECIMAL_M_PI-DECIMAL(0.0000001) : DECIMAL_ACOS(dot); +} + +inline decimal Angle(const Vec3 &vec1, const Vec3 &vec2) { + return AngleUnit(vec1.normalized(), vec2.normalized()); +} + ///// Definition of custom ASSERT_* statements ///// #define DEFAULT_TOLERANCE DECIMAL(1e-3) constexpr auto vectorEqual = [](const Vec2 &a, const Vec2 &b) { - return abs(a.x - b.x) < DEFAULT_TOLERANCE && abs(a.y - b.y) < DEFAULT_TOLERANCE; + return abs(a.x() - b.x()) < DEFAULT_TOLERANCE && abs(a.y() - b.y()) < DEFAULT_TOLERANCE; }; constexpr auto Vec3Equal = [](const Vec3 &a, const Vec3 &b) { - return abs(a.x - b.x) < DEFAULT_TOLERANCE && abs(a.y - b.y) < DEFAULT_TOLERANCE - && abs(a.z - b.z) < DEFAULT_TOLERANCE; + return abs(a.x() - b.x()) < DEFAULT_TOLERANCE && abs(a.y() - b.y()) < DEFAULT_TOLERANCE + && abs(a.z() - b.z()) < DEFAULT_TOLERANCE; }; constexpr auto LocationRecordEqual = [](const LocationRecord &a, const LocationRecord &b) { @@ -42,23 +53,23 @@ MATCHER_P(LocationRecordsEqual, expected, "") { #define ASSERT_DECIMAL_EQ_DEFAULT(val1, val2) ASSERT_DECIMAL_EQ(val1, val2, DEFAULT_TOLERANCE) #define ASSERT_VEC2_EQ(val1, val2, tolerance) \ - ASSERT_DECIMAL_EQ(val1.x, val2.x, tolerance); \ - ASSERT_DECIMAL_EQ(val1.y, val2.y, tolerance) + ASSERT_DECIMAL_EQ(val1.x(), val2.x(), tolerance); \ + ASSERT_DECIMAL_EQ(val1.y(), val2.y(), tolerance) #define ASSERT_VEC2_EQ_DEFAULT(val1, val2) ASSERT_VEC2_EQ(val1, val2, DEFAULT_TOLERANCE) #define ASSERT_VEC3_EQ(val1, val2, tolerance) \ - ASSERT_DECIMAL_EQ(val1.x, val2.x, tolerance); \ - ASSERT_DECIMAL_EQ(val1.y, val2.y, tolerance); \ - ASSERT_DECIMAL_EQ(val1.z, val2.z, tolerance) + ASSERT_DECIMAL_EQ(val1.x(), val2.x(), tolerance); \ + ASSERT_DECIMAL_EQ(val1.y(), val2.y(), tolerance); \ + ASSERT_DECIMAL_EQ(val1.z(), val2.z(), tolerance) #define ASSERT_VEC3_EQ_DEFAULT(val1, val2) ASSERT_VEC3_EQ(val1, val2, DEFAULT_TOLERANCE) #define ASSERT_QUAT_EQ(val1, val2, tolerance) \ - ASSERT_DECIMAL_EQ(val1.real, val2.real, tolerance); \ - ASSERT_DECIMAL_EQ(val1.i, val2.i, tolerance); \ - ASSERT_DECIMAL_EQ(val1.j, val2.j, tolerance); \ - ASSERT_DECIMAL_EQ(val1.k, val2.k, tolerance) + ASSERT_DECIMAL_EQ(val1.w(), val2.w(), tolerance); \ + ASSERT_DECIMAL_EQ(val1.x(), val2.x(), tolerance); \ + ASSERT_DECIMAL_EQ(val1.y(), val2.y(), tolerance); \ + ASSERT_DECIMAL_EQ(val1.z(), val2.z(), tolerance) #define ASSERT_QUAT_EQ_DEFAULT(val1, val2) ASSERT_QUAT_EQ(val1, val2, DEFAULT_TOLERANCE) @@ -66,8 +77,8 @@ MATCHER_P(LocationRecordsEqual, expected, "") { { \ Quaternion quat1 = SphericalToQuaternion(val1); \ Quaternion quat2 = SphericalToQuaternion(val2); \ - if (quat1.real < 0) quat1 = -quat1; \ - if (quat2.real < 0) quat2 = -quat2; \ + if (quat1.w() < 0) quat1 = Quaternion(-quat1.w(), -quat1.x(), -quat1.y(), -quat1.z()); \ + if (quat2.w() < 0) quat2 = Quaternion(-quat2.w(), -quat2.x(), -quat2.y(), -quat2.z()); \ ASSERT_QUAT_EQ(quat1, quat2, tolerance); \ } @@ -148,17 +159,17 @@ struct ImageData { ImageData(const char *path, decimal focal_length, decimal pixel_size, EulerAngles orientation, Vec3 position) : path(path), focal_length(focal_length), FocalLength(std::to_string(focal_length)), pixel_size(pixel_size), PixelSize(std::to_string(pixel_size)), orientation(orientation), position(position) { - Orientation += orientation.ra; + Orientation += orientation.x(); Orientation += ","; - Orientation += orientation.de; + Orientation += orientation.y(); Orientation += ","; - Orientation += orientation.roll; + Orientation += orientation.z(); - Position += position.x; + Position += position.x(); Position += ","; - Position += position.y; + Position += position.y(); Position += ","; - Position += position.z; + Position += position.z(); } }; diff --git a/test/common/spatial/TLS-test.cpp b/test/common/spatial/TLS-test.cpp new file mode 100644 index 00000000..feeb639a --- /dev/null +++ b/test/common/spatial/TLS-test.cpp @@ -0,0 +1,133 @@ +#include +#include + +#include "test/common/common.hpp" + +#include "common/logging.hpp" + +#include "src/common/spatial/attitude-utils.hpp" +#include "src/common/decimal.hpp" +#include +#include +#include +using namespace std; +#define MAX_COLS_TO_TEST 10 +#define MONTE_CARLO_ITERATIONS 10 +#define MAX_NOISE_TO_TEST 0.5 +#define MAX_COEFFICIENT 1000 +#define DECIMAL_PLACES 100 // 1/DECIMAL_PLACES is used to determine how many decimal places we go to i.e 100 would be x.xx, 1000 would be x.xxx +#define ERROR_STEPS 30 +#define TLS_TOLERANCE DECIMAL(0.05) + +#define VECTOR3_EQUALS(vec1, vec2, tolerance) \ + EXPECT_LT(abs(vec1(0) - vec2(0)), tolerance); \ + EXPECT_LT(abs(vec1(1) - vec2(1)), tolerance); \ + EXPECT_LT(abs(vec1(2) - vec2(2)), tolerance); + +#define VECTOR2_EQUALS(vec1, vec2, tolerance) \ + EXPECT_LT(abs(vec1(0) - vec2(0)), tolerance); \ + EXPECT_LT(abs(vec1(1) - vec2(1)), tolerance); + +TEST(TLSTest, vec3Test) +{ + Eigen::Matrix data{ + {1.0, 1.0, 1.0, 3.0}, + {1.0, 2.0, 3.0, 6.0}, + {0.0, 0.0, 0.0, 0.0}, + {-1.0, -1.0, -1.0, -3.0}, + {-1.0, 3.0, 0.0, 2.0}, + {4329.0, -4211.0, 0.0, 118.0}}; + Eigen::Vector expected(1, 1, 1); + Eigen::Vector actual = found::TLS(data); + VECTOR3_EQUALS(actual, expected, DEFAULT_TOLERANCE); +} + +void TestPermuations(int rows, int cols, std::default_random_engine rando, ofstream *MyFile) +{ + int modulo = MAX_COEFFICIENT * DECIMAL_PLACES; + decimal divisor = DECIMAL_PLACES; + // Technically this isn't monte carlo but we can use the same variable + for (int itera = 0; itera < MONTE_CARLO_ITERATIONS; itera++) + { + for (decimal noise = 0; noise < MAX_NOISE_TO_TEST; noise += DECIMAL(MAX_NOISE_TO_TEST) / ERROR_STEPS) + { + std::stringstream ss; + decimal smallPercent = (noise/MAX_NOISE_TO_TEST)*(DECIMAL(1.0)/MONTE_CARLO_ITERATIONS)*(DECIMAL(1.0)/(MAX_COLS_TO_TEST-2)); + decimal mediumPercent = (DECIMAL(itera)/MONTE_CARLO_ITERATIONS)*(DECIMAL(1.0)/(MAX_COLS_TO_TEST-2)); + decimal bigPercent = (DECIMAL(cols-3)/(MAX_COLS_TO_TEST-2)); + ss << "cols: " << cols << ", noise: " << noise << ";\t" << (smallPercent + mediumPercent + bigPercent) * 100 << "%\n"; + LOG_INFO(ss.str()); + Eigen::Vector expected; + expected.resize(cols - 1); + for (int j = 0; j < cols - 1; j++) + { + expected(j) = (rando() % modulo) / divisor; + } + + Eigen::Matrix testMat; + testMat.resize(rows, cols); + for (int i = 0; i < rows; i++) + { + decimal finalValue = 0; + for (int j = 0; j < cols - 1; j++) + { + decimal coefficient = (rando() % modulo) / divisor; + finalValue += coefficient * expected(j); + testMat(i, j) = coefficient; + } + testMat(i, cols - 1) = finalValue; + } + decimal averageError = 0; + for (int iter = 0; iter < MONTE_CARLO_ITERATIONS; iter++) + { + Eigen::Matrix noisyMatrix; + noisyMatrix.resize(rows, cols); + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + noisyMatrix(i, j) = testMat(i, j) + testMat(i, j) * (rando() % 1)*2-1 * noise; + } + //noisyMatrix(i, cols-1) = testMat(i, cols-1); + } + Eigen::Vector actual = found::TLS(noisyMatrix); + if ((actual-expected).norm() < DEFAULT_TOLERANCE) continue; + averageError += abs((actual-expected).norm())/expected.norm(); + } + averageError /= MONTE_CARLO_ITERATIONS; + if (averageError > 10000) continue; + *MyFile << rows << "," << cols << "," << noise << "," << averageError << "," << expected.norm() << "\n"; + } + } +} + +// I'm putting the permuations for performance testing in here cause i'm too lazy to figure out how to do it properly. +// I'll remove it later +TEST(TLSTest, vec2andPermutationTest) +{ + Eigen::Matrix data{ + {0.239794, 0.0, 0.719382}, + {1.0, 2.0, 5.0}, + {0.0, 0.0, 0.0}, + {-1.0, 5.0, 2.0}}; + Eigen::Vector2d expected(3, 1); + Eigen::Vector2d actual = found::TLS(data); + VECTOR2_EQUALS(actual, expected, DEFAULT_TOLERANCE); + + ofstream MyFile("./TLS-data.csv"); + MyFile << "rows,cols,noise,error,expected mag\n"; + std::default_random_engine rando; + + for (int i = 3; i <= MAX_COLS_TO_TEST; i++) + { + std::stringstream ss; + ss << "crunchy time\n"; + LOG_INFO(ss.str()); + TestPermuations(i + (rando() % 5) + 2, i, rando, &MyFile); + std::stringstream ss1; + ss1 << "we did it"; + LOG_INFO(ss1.str()); + } + + MyFile.close(); +} \ No newline at end of file diff --git a/test/datafile/serialization-test.cpp b/test/datafile/serialization-test.cpp index bc897bfa..61618761 100644 --- a/test/datafile/serialization-test.cpp +++ b/test/datafile/serialization-test.cpp @@ -81,7 +81,9 @@ TEST_F(SerializationTest, CorrectHeader) { TEST_F(SerializationTest, IncorrectHeader) { std::string incorrectStr(reinterpret_cast(incorrectCRCTestHeader), sizeof(incorrectCRCTestHeader)); std::istringstream stream(incorrectStr); + testing::internal::CaptureStderr(); ASSERT_THROW(readHeader(stream), std::runtime_error); + std::string output = testing::internal::GetCapturedStderr(); } /** @@ -101,7 +103,7 @@ TEST_F(SerializationTest, RoundTripSerialization) { memcpy(expected.header.magic, "FOUN", 4); expected.header.version = 1; expected.header.num_positions = 2; - expected.relative_attitude = {0, 123456789., 987654321., 111111111.}; + expected.relative_attitude = Quaternion(0, 123456789., 987654321., 111111111.); LocationRecord loc1{161803398, {100, 200, 300}}; LocationRecord loc2{271828182, {-100, -200, -300}}; @@ -278,7 +280,7 @@ TEST_F(SerializationTest, RoundTripSerializationEmptyPositions) { memcpy(data.header.magic, "FOUN", 4); data.header.version = 1; data.header.num_positions = 0; - data.relative_attitude = {0, 0, 0, 0}; + data.relative_attitude = Quaternion(0, 0, 0, 0); // No positions added std::ostringstream out; diff --git a/test/distance/distance-test/iterative-test.cpp b/test/distance/distance-test/iterative-test.cpp index ab551261..9059089f 100644 --- a/test/distance/distance-test/iterative-test.cpp +++ b/test/distance/distance-test/iterative-test.cpp @@ -62,9 +62,9 @@ using found::IterativeSphericalDistanceDeterminationAlgorithm; * vec2, on a component basis, within tolerance */ #define VECTOR_EQUALS(vec1, vec2, tolerance) \ - EXPECT_LT(abs(vec1.x - vec2.x), tolerance); \ - EXPECT_LT(abs(vec1.y - vec2.y), tolerance); \ - EXPECT_LT(abs(vec1.z - vec2.z), tolerance); + EXPECT_LT(abs(vec1.x() - vec2.x()), tolerance); \ + EXPECT_LT(abs(vec1.y() - vec2.y()), tolerance); \ + EXPECT_LT(abs(vec1.z() - vec2.z()), tolerance); TEST(IterativeSphericalDistanceDeterminationAlgorithmTest, TestConstructorLowPowers) { // Taken from TestCenteredEarthRandom7 @@ -348,7 +348,7 @@ TEST(IterativeSphericalDistanceDeterminationAlgorithmTest, TestCenteredEarthY2) TEST(IterativeSphericalDistanceDeterminationAlgorithmTest, TestCenteredEarthY3) { // Step 0: Determine Quaterion rotation found::Quaternion positionDirection = found::SphericalToQuaternion( - static_cast (M_PI / 2), static_cast(0), static_cast(0)).Conjugate(); + static_cast (M_PI / 2), static_cast(0), static_cast(0)).conjugate(); // Step I: Pick some distance (m) and a Camera decimal y_E = RADIUS_OF_EARTH + 10000000; @@ -372,9 +372,9 @@ TEST(IterativeSphericalDistanceDeterminationAlgorithmTest, TestCenteredEarthY2) Vec3 p2 = {centerMag, -projectionRadiusMag, 0}; Vec3 p3 = {centerMag, -projectionRadiusMag * DECIMAL_COS(-0.1), projectionRadiusMag * DECIMAL_SIN(-0.1)}; - Vec3 p1Rotated = positionDirection.Rotate(p1); - Vec3 p2Rotated = positionDirection.Rotate(p2); - Vec3 p3Rotated = positionDirection.Rotate(p3); + Vec3 p1Rotated = positionDirection * p1; + Vec3 p2Rotated = positionDirection * p2; + Vec3 p3Rotated = positionDirection * p3; // Step III: Use CTS to convert to 2D vectors Points pts = {cam.SpatialToCamera(p1Rotated), diff --git a/test/distance/distance-test/spherical-test.cpp b/test/distance/distance-test/spherical-test.cpp index de720675..9175fc07 100644 --- a/test/distance/distance-test/spherical-test.cpp +++ b/test/distance/distance-test/spherical-test.cpp @@ -47,12 +47,14 @@ using found::SphericalDistanceDeterminationAlgorithm; * vec2, on a component basis, within tolerance */ #define VECTOR_EQUALS(vec1, vec2, tolerance) \ - EXPECT_LT(abs(vec1.x - vec2.x), tolerance); \ - EXPECT_LT(abs(vec1.y - vec2.y), tolerance); \ - EXPECT_LT(abs(vec1.z - vec2.z), tolerance); + EXPECT_LT(abs(vec1.x() - vec2.x()), tolerance); \ + EXPECT_LT(abs(vec1.y() - vec2.y()), tolerance); \ + EXPECT_LT(abs(vec1.z() - vec2.z()), tolerance); std::ostream &operator<<(std::ostream &stream, const Vec3 &vector) { - stream << std::fixed << std::setprecision(5) << "(" << vector.x << ", " << vector.y << ", " << vector.z << ")"; + stream << std::fixed << std::setprecision(5) + << "(" << vector.x() << ", " << vector.y() + << ", " << vector.z() << ")"; return stream; } @@ -192,7 +194,7 @@ TEST(SphericalDistanceDeterminationAlgorithmTest, TestCenteredEarthY2) { TEST(SphericalDistanceDeterminationAlgorithmTest, TestCenteredEarthY3) { // Step 0: Determine Quaterion rotation found::Quaternion positionDirection = found::SphericalToQuaternion( - static_cast (M_PI / 2), static_cast(0), static_cast(0)).Conjugate(); + static_cast (M_PI / 2), static_cast(0), static_cast(0)).conjugate(); // Step I: Pick some distance (m) and a Camera decimal y_E = RADIUS_OF_EARTH + 10000000; @@ -223,9 +225,9 @@ TEST(SphericalDistanceDeterminationAlgorithmTest, TestCenteredEarthY2) { // LOG_INFO(ss1.str()); - Vec3 p1Rotated = positionDirection.Rotate(p1); - Vec3 p2Rotated = positionDirection.Rotate(p2); - Vec3 p3Rotated = positionDirection.Rotate(p3); + Vec3 p1Rotated = positionDirection * p1; + Vec3 p2Rotated = positionDirection * p2; + Vec3 p3Rotated = positionDirection * p3; // std::stringstream ss; // ss << p1Rotated.x << " " << p1Rotated.y << " " << p1Rotated.z; diff --git a/test/distance/edge-test/simple-edge-detection-test.cpp b/test/distance/edge-test/simple-edge-detection-test.cpp index bba93f89..4998a940 100644 --- a/test/distance/edge-test/simple-edge-detection-test.cpp +++ b/test/distance/edge-test/simple-edge-detection-test.cpp @@ -14,8 +14,8 @@ namespace found { MATCHER_P(Vec2Equal, expected, "") { - return abs(expected.x - arg.x) < DEFAULT_TOLERANCE && - abs(expected.y - arg.y) < DEFAULT_TOLERANCE; + return abs(expected.x() - arg.x()) < DEFAULT_TOLERANCE && + abs(expected.y() - arg.y()) < DEFAULT_TOLERANCE; } TEST(SimpleEdgeDetectionTest, TestNoEdgeAllSpace) { diff --git a/test/distance/output-test.cpp b/test/distance/output-test.cpp index 8696a765..d9efc7aa 100644 --- a/test/distance/output-test.cpp +++ b/test/distance/output-test.cpp @@ -14,10 +14,10 @@ TEST(DistanceOutputTest, TestGetEarthCoordinatesNoRotationSimpleVector) { EarthSphericalVec3 actual = GetEarthCoordinates(celestialVector, gmst); EulerAngles expected = {0, 0, 0}; - ASSERT_DECIMAL_EQ_DEFAULT(expected.ra > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.ra : expected.ra, + ASSERT_DECIMAL_EQ_DEFAULT(expected.x() > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.x() : expected.x(), DegToRad(actual.longitude)); - ASSERT_DECIMAL_EQ_DEFAULT(expected.de, DegToRad(actual.lattitude)); - ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.Magnitude(), actual.radius); + ASSERT_DECIMAL_EQ_DEFAULT(expected.y(), DegToRad(actual.lattitude)); + ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.norm(), actual.radius); ASSERT_DECIMAL_EQ_DEFAULT(gmst, actual.GMST); } @@ -27,10 +27,10 @@ TEST(DistanceOutputTest, TestGetEarthCoordinates90RotationSimpleVector) { EarthSphericalVec3 actual = GetEarthCoordinates(celestialVector, gmst); EulerAngles expected = {-DECIMAL_M_PI / 2, 0, 0}; - ASSERT_DECIMAL_EQ_DEFAULT(expected.ra > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.ra : expected.ra, + ASSERT_DECIMAL_EQ_DEFAULT(expected.x() > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.x() : expected.x(), DegToRad(actual.longitude)); - ASSERT_DECIMAL_EQ_DEFAULT(expected.de, DegToRad(actual.lattitude)); - ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.Magnitude(), actual.radius); + ASSERT_DECIMAL_EQ_DEFAULT(expected.y(), DegToRad(actual.lattitude)); + ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.norm(), actual.radius); ASSERT_DECIMAL_EQ_DEFAULT(gmst, actual.GMST); } @@ -40,10 +40,10 @@ TEST(DistanceOutputTest, TestGetEarthCoordinatesGeneralRotationSimpleVector) { EarthSphericalVec3 actual = GetEarthCoordinates(celestialVector, gmst); EulerAngles expected = {DegToRad(gmst), 0, 0}; - ASSERT_DECIMAL_EQ_DEFAULT((expected.ra > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.ra : expected.ra), + ASSERT_DECIMAL_EQ_DEFAULT((expected.x() > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.x() : expected.x()), DegToRad(actual.longitude)); - ASSERT_DECIMAL_EQ_DEFAULT(expected.de, DegToRad(actual.lattitude)); - ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.Magnitude(), actual.radius); + ASSERT_DECIMAL_EQ_DEFAULT(expected.y(), DegToRad(actual.lattitude)); + ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.norm(), actual.radius); ASSERT_DECIMAL_EQ_DEFAULT(gmst, actual.GMST); } @@ -51,14 +51,14 @@ TEST(DistanceOutputTest, TestGetEarthCoordinatesGeneral1) { Vec3 celestialVector{1, 2, 3}; decimal gmst = 54; EarthSphericalVec3 actual = GetEarthCoordinates(celestialVector, gmst); - EulerAngles expected = {-DegToRad(gmst) + DECIMAL_ATAN2(celestialVector.y, celestialVector.x), - DECIMAL_ASIN(celestialVector.z/celestialVector.Magnitude()), + EulerAngles expected = {-DegToRad(gmst) + DECIMAL_ATAN2(celestialVector.y(), celestialVector.x()), + DECIMAL_ASIN(celestialVector.z()/celestialVector.norm()), 0}; - ASSERT_DECIMAL_EQ_DEFAULT((expected.ra > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.ra : expected.ra), + ASSERT_DECIMAL_EQ_DEFAULT((expected.x() > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.x() : expected.x()), DegToRad(actual.longitude)); - ASSERT_DECIMAL_EQ_DEFAULT(expected.de, DegToRad(actual.lattitude)); - ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.Magnitude(), actual.radius); + ASSERT_DECIMAL_EQ_DEFAULT(expected.y(), DegToRad(actual.lattitude)); + ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.norm(), actual.radius); ASSERT_DECIMAL_EQ_DEFAULT(gmst, actual.GMST); } @@ -67,15 +67,15 @@ TEST(DistanceOutputTest, TestGetEarthCoordinatesGeneral2) { decimal gmst = 528; EarthSphericalVec3 actual = GetEarthCoordinates(celestialVector, gmst); EulerAngles expected = {-DegToRad(std::fmod(gmst, 360.0)) - + DECIMAL_ATAN2(celestialVector.y, celestialVector.x) + + DECIMAL_ATAN2(celestialVector.y(), celestialVector.x()) + 2 * DECIMAL_M_PI, // I manually adjust for this - DECIMAL_ASIN(celestialVector.z/celestialVector.Magnitude()), + DECIMAL_ASIN(celestialVector.z()/celestialVector.norm()), 0}; - ASSERT_DECIMAL_EQ_DEFAULT((expected.ra > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.ra : expected.ra), + ASSERT_DECIMAL_EQ_DEFAULT((expected.x() > DECIMAL_M_PI ? 2 * DECIMAL_M_PI - expected.x() : expected.x()), DegToRad(actual.longitude)); - ASSERT_DECIMAL_EQ_DEFAULT(expected.de, DegToRad(actual.lattitude)); - ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.Magnitude(), actual.radius); + ASSERT_DECIMAL_EQ_DEFAULT(expected.y(), DegToRad(actual.lattitude)); + ASSERT_DECIMAL_EQ_DEFAULT(celestialVector.norm(), actual.radius); ASSERT_DECIMAL_EQ_DEFAULT(gmst, actual.GMST); } diff --git a/test/distance/vectorize-test.cpp b/test/distance/vectorize-test.cpp index 3e888590..bdd366a9 100644 --- a/test/distance/vectorize-test.cpp +++ b/test/distance/vectorize-test.cpp @@ -20,15 +20,15 @@ namespace found { * away from the absolute frame) * * @note This performs a backwards rotation, - * and is equivalent to q.Conjugate().Rotate(v). + * and is equivalent to q.conjugate() * v. * I write this alternate way as a way to do it * in two independent ways. */ Vec3 ProjectVector(Vec3 &v, Quaternion &q) { - Vec3 x = q.Rotate({1, 0, 0}); - Vec3 y = q.Rotate({0, 1, 0}); - Vec3 z = q.Rotate({0, 0, 1}); - return {(v*x)/(x*x), (v*y)/(y*y), (v*z)/(z*z)}; + Vec3 x = q * Vec3(1, 0, 0); + Vec3 y = q * Vec3(0, 1, 0); + Vec3 z = q * Vec3(0, 0, 1); + return Vec3(v.dot(x)/x.dot(x), v.dot(y)/y.dot(y), v.dot(z)/z.dot(z)); } TEST(LOSTVectorGenerationAlgorithmTest, TestIdentityTest) { @@ -70,9 +70,9 @@ TEST(LOSTVectorGenerationAlgorithmTest, TestSimpleZRotationTest1) { PositionVector x_E = {100.0, 200.0, 300.0}; PositionVector actual = vectorGen.Run(-x_E); - PositionVector expected = {x_E.x * DECIMAL_COS(DECIMAL_M_PI / 4) - x_E.y * DECIMAL_SIN(DECIMAL_M_PI / 4), - x_E.x * DECIMAL_SIN(DECIMAL_M_PI / 4) + x_E.y * DECIMAL_COS(DECIMAL_M_PI / 4), - x_E.z}; + PositionVector expected = {x_E.x() * DECIMAL_COS(DECIMAL_M_PI / 4) - x_E.y() * DECIMAL_SIN(DECIMAL_M_PI / 4), + x_E.x() * DECIMAL_SIN(DECIMAL_M_PI / 4) + x_E.y() * DECIMAL_COS(DECIMAL_M_PI / 4), + x_E.z()}; ASSERT_VEC3_EQ_DEFAULT(expected, actual); } @@ -87,9 +87,9 @@ TEST(LOSTVectorGenerationAlgorithmTest, TestSimpleZRotationTest2) { PositionVector x_E = {100.0, 200.0, 300.0}; PositionVector actual = vectorGen.Run(x_E); - PositionVector expected = {-x_E.x * DECIMAL_COS(DECIMAL_M_PI / 3) + x_E.y * DECIMAL_SIN(DECIMAL_M_PI / 3), - -x_E.x * DECIMAL_SIN(DECIMAL_M_PI / 3) - x_E.y * DECIMAL_COS(DECIMAL_M_PI / 3), - -x_E.z}; + PositionVector expected = {-x_E.x() * DECIMAL_COS(DECIMAL_M_PI / 3) + x_E.y() * DECIMAL_SIN(DECIMAL_M_PI / 3), + -x_E.x() * DECIMAL_SIN(DECIMAL_M_PI / 3) - x_E.y() * DECIMAL_COS(DECIMAL_M_PI / 3), + -x_E.z()}; ASSERT_VEC3_EQ_DEFAULT(expected, actual); } @@ -131,8 +131,8 @@ TEST(LOSTVectorGenerationAlgorithmTest, TestRotationIntoArbitraryReferenceAndRel TEST(LOSTVectorGenerationAlgorithmTest, TestGeneral) { // Setup Dependencies - Quaternion referenceOrientation({1.0, 2.0, 3.0}); - Quaternion relativeOrientation({4.0, 5.0, 6.0}); + Quaternion referenceOrientation = Quaternion(0, 1.0, 2.0, 3.0).normalized(); + Quaternion relativeOrientation = Quaternion(0, 4.0, 5.0, 6.0).normalized(); LOSTVectorGenerationAlgorithm vectorGen(relativeOrientation, referenceOrientation); // Create a PositionVector to test with @@ -140,8 +140,8 @@ TEST(LOSTVectorGenerationAlgorithmTest, TestGeneral) { PositionVector actual = vectorGen.Run(x_E); // Should be equivalent to this: - PositionVector expected = (QuaternionToDCM(referenceOrientation.Conjugate()) - * QuaternionToDCM(relativeOrientation.Conjugate())) * -x_E; + PositionVector expected = (QuaternionToDCM(referenceOrientation.conjugate()) + * QuaternionToDCM(relativeOrientation.conjugate())) * -x_E; ASSERT_VEC3_EQ_DEFAULT(expected, actual); } diff --git a/test/example/example-test.cpp b/test/example/example-test.cpp index 00eeedbd..77fc1340 100644 --- a/test/example/example-test.cpp +++ b/test/example/example-test.cpp @@ -99,16 +99,16 @@ TEST_F(ExampleTest, MySecondTest) { // Now, we test if the result of our // function matched our expectation - ASSERT_EQ(expectedPoint1.x, p.at(0).x); - ASSERT_EQ(expectedPoint1.y, p.at(0).y); + ASSERT_EQ(expectedPoint1.x(), p.at(0).x()); + ASSERT_EQ(expectedPoint1.y(), p.at(0).y()); // This is EXPECT_*. It parallels ASSERT_* // statments, but unlike ASSERT_*, EXPECT_* // continues the test case even if the condition // inside fails. However, this will still fail // the entire test case - EXPECT_EQ(expectedPoint2.x, p.at(1).x); - EXPECT_EQ(expectedPoint2.y, p.at(1).y); + EXPECT_EQ(expectedPoint2.x(), p.at(1).x()); + EXPECT_EQ(expectedPoint2.y(), p.at(1).y()); ASSERT_EQ(expectedSize, p.size()); } diff --git a/test/integration/integration-test.cpp b/test/integration/integration-test.cpp index 6d9157ba..a58a9097 100644 --- a/test/integration/integration-test.cpp +++ b/test/integration/integration-test.cpp @@ -36,14 +36,22 @@ TEST_F(IntegrationTest, TestMainNothing) { int argc = 1; const char *argv[1] = {"found"}; + testing::internal::CaptureStderr(); // Ignore error to pass PR test + ASSERT_EQ(EXIT_FAILURE, main(argc, const_cast(argv))); + + std::string output = testing::internal::GetCapturedStderr(); } TEST_F(IntegrationTest, TestMainNoOption) { int argc = 3; const char *argv[3] = {"found", "--png", "none.png"}; + testing::internal::CaptureStderr(); // Ignore error to pass PR test + ASSERT_EQ(EXIT_FAILURE, main(argc, const_cast(argv))); + + std::string output = testing::internal::GetCapturedStderr(); } TEST_F(IntegrationTest, TestMainHelp) { @@ -74,7 +82,11 @@ TEST_F(IntegrationTest, TestMainCalibrationBadDistanceAlgorithm) { int argc = 4; const char *argv[] = {"found", "distance", "--distance-algo", "NEDDA"}; + testing::internal::CaptureStderr(); // Ignore error to pass PR test + ASSERT_THROW(main(argc, const_cast(argv)), std::runtime_error); + + std::string output = testing::internal::GetCapturedStderr(); } TEST_F(IntegrationTest, TestMainCalibrationOptionBlank) { @@ -108,7 +120,7 @@ TEST_F(IntegrationTest, TestMainCalibrationGeneral) { Quaternion ref = SphericalToQuaternion(EulerAngles(DegToRad(1.1), DegToRad(1.2), DegToRad(1.3))); Quaternion loc = SphericalToQuaternion(EulerAngles(DegToRad(1.4), DegToRad(1.5), DegToRad(1.6))); - Quaternion rel = ref * loc.Conjugate(); + Quaternion rel = ref * loc.conjugate(); DataFile expected{ {}, @@ -188,8 +200,8 @@ TEST_F(IntegrationTest, TestIndependentDistancePipeline) { ASSERT_EQ(static_cast(1), actual.header.num_positions); ASSERT_QUAT_EQ_DEFAULT(Quaternion(1, 0, 0, 0), actual.relative_attitude); ASSERT_GE(DEFAULT_MAG_ERR_TOL, - (example_earth1.position.Magnitude() - actual.positions[0].position.Magnitude()) - / example_earth1.position.Magnitude()); + (example_earth1.position.norm() - actual.positions[0].position.norm()) + / example_earth1.position.norm()); ASSERT_GE(DEFAULT_ARC_SEC_TOL, RadToArcSec(Angle(example_earth1.position, actual.positions[0].position))); } @@ -216,8 +228,8 @@ TEST_F(IntegrationTest, TestIndependentDistancePipelineWithISDDA) { ASSERT_EQ(static_cast(1), actual.header.num_positions); ASSERT_QUAT_EQ_DEFAULT(Quaternion(1, 0, 0, 0), actual.relative_attitude); ASSERT_GE(DEFAULT_MAG_ERR_TOL, - (example_earth1.position.Magnitude() - actual.positions[0].position.Magnitude()) - / example_earth1.position.Magnitude()); + (example_earth1.position.norm() - actual.positions[0].position.norm()) + / example_earth1.position.norm()); ASSERT_GE(DEFAULT_ARC_SEC_TOL, RadToArcSec(Angle(example_earth1.position, actual.positions[0].position))); } @@ -252,8 +264,8 @@ TEST_F(IntegrationTest, TestCalibrationDistanceCombinedPipeline) { ASSERT_EQ(static_cast(1), actual.header.num_positions); ASSERT_QUAT_EQ(SphericalToQuaternion(example_earth1.orientation), actual.relative_attitude, 1); ASSERT_GE(DEFAULT_MAG_ERR_TOL, - (example_earth1.position.Magnitude() - actual.positions[0].position.Magnitude()) - / example_earth1.position.Magnitude()); + (example_earth1.position.norm() - actual.positions[0].position.norm()) + / example_earth1.position.norm()); ASSERT_GE(DEFAULT_ARC_SEC_TOL, RadToArcSec(Angle(example_earth1.position, actual.positions[0].position))); } @@ -291,8 +303,8 @@ TEST_F(IntegrationTest, TestCalibrationDistanceCombinedPipelineOtherOutput) { ASSERT_EQ(static_cast(1), actual.header.num_positions); ASSERT_QUAT_EQ(SphericalToQuaternion(example_earth1.orientation), actual.relative_attitude, 1); ASSERT_GE(DEFAULT_MAG_ERR_TOL, - (example_earth1.position.Magnitude() - actual.positions[0].position.Magnitude()) - / example_earth1.position.Magnitude()); + (example_earth1.position.norm() - actual.positions[0].position.norm()) + / example_earth1.position.norm()); ASSERT_GE(DEFAULT_ARC_SEC_TOL, RadToArcSec(Angle(example_earth1.position, actual.positions[0].position))); std::remove(other_path); diff --git a/test/providers/converters-test.cpp b/test/providers/converters-test.cpp index 68b5d4cf..2bbd3d26 100644 --- a/test/providers/converters-test.cpp +++ b/test/providers/converters-test.cpp @@ -21,27 +21,27 @@ TEST(ConvertersTest, TestEAComma) { std::string str = "0,45,90"; EulerAngles angles = strtoea(str); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), angles.ra); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL_M_PI / 4, angles.de); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL_M_PI / 2, angles.roll); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(0), angles.x()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL_M_PI / 4, angles.y()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL_M_PI / 2, angles.z()); } TEST(ConvertersTest, TestEASpace) { std::string str = "4.2 3.9 -9.5"; EulerAngles angles = strtoea(str); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(4.2)), angles.ra); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(3.9)), angles.de); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(-9.5)), angles.roll); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(4.2)), angles.x()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(3.9)), angles.y()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(-9.5)), angles.z()); } TEST(ConvertersTest, TestEAIncomplete) { std::string str = "4.2 3.9"; EulerAngles angles = strtoea(str); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(4.2)), angles.ra); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(3.9)), angles.de); - ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(0)), angles.roll); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(4.2)), angles.x()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(3.9)), angles.y()); + ASSERT_DECIMAL_EQ_DEFAULT(DECIMAL(DegToRad(0)), angles.z()); } TEST(ConvertersTest, TestBoolFalse) { diff --git a/tools/README.md b/tools/README.md deleted file mode 100644 index 65bee31d..00000000 --- a/tools/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# FOUND Tools -This folder hosts tools that can be used with FOUND for analysis or artificial generation. - -# Usage -## Python Environment Setup -If you are using the Dev Container the Python environments are already setup so you can skip this step. - -To setup a virutal environment for a tool your going to want an environment mananger (conda, mamba, etc.) to install the dependencies. You can install `micromamba` by running: -```bash -"${SHELL}" <(curl -L micro.mamba.pm/install.sh) && / -micromamba shell init -s -p ~/micromamba && / -source ~/.bashrc # or ~/.zshrc, depending on your shell -``` -Once you've installed the package manager of your choice, create the virtual environments by running `setup-tools-envs.sh` in the `found` directory. - -## Using Tools -1. Activate the environment for that tool (`micromamba activate `) -2. Invoke the tool (`python -m tools. [arguments for tool]`) - -For more information, visit each tool's folder for its `README.md`. - -# Tools -## 1. Generator - -Generates artificial images of Earth from a given position and orientation -- Tool Name: `generator` -- Enviornment Name: `generator_env` - -## 2. Attitude - -Generates artificial attitudes for FOUND to use with the calibration and distance pipelines -- Tool Name: `attitude` -- Environment Name: `attitude_env` \ No newline at end of file diff --git a/tools/__init__.py b/tools/__init__.py deleted file mode 100644 index d0ddb3c7..00000000 --- a/tools/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys -from pathlib import Path - -# Add this folder temporarily to the system path -sys.path.insert(0, str(Path(__file__).resolve().parents[0])) diff --git a/tools/attitude/README.md b/tools/attitude/README.md deleted file mode 100644 index 423cf271..00000000 --- a/tools/attitude/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# Attitude - -The `attitude` tool generates orientations to use for the calibration and distance modes of FOUND. It also generates commands to use with FOUND and `tools.generator` for ease. - -Terms: -1. Local Attitude: The attitude of FOUND (the camera associated with it) -2. Reference Attitude: Another attitude, locked relative to FOUND -3. Calibration Attitude Pair: The Local and Reference Attitudes used to calibrate FOUND -4. Distance Attitude Pair: The Local and Reference Attitudes used during position inference for FOUND (distance) - -# Flags - -Optional Flags: -0. `--help`, `-h`: Help -2. `--local-attitude`: A local attitude to use for one of the local/reference attitude pairs for distance. If specified, this will appear in the first pair for use with the distance pipeline. -3. `--calibration-attitude`: The calibration attitude to use (describes transformation from reference to local frame) -4. `--num-attitude-pairs`: The number of attitude pairs to generate for the distance mode of FOUND - -# Example Usage - -From `found` root: -```bash ->>> python3 -m tools.attitude --local-attitude 45 0 0 --num-attitude-pairs 3 - -========== CALIBRATION ATTITUDES ========== - -Calibration Attitude: RA: 14.756, DE: 77.795, ROLL: 319.274 - -Local Attitude: RA: 272.236, DE: -29.259, ROLL: 37.154 -Reference Attitude: RA: 228.843, DE: 38.130, ROLL: 84.839 - -Calibration Command: -./build/bin/found calibration \ ---reference-orientation "228.842948936861 38.12967370616513 84.83945989677017" \ ---local-orientation "272.23611078254874 -29.25898525400815 37.15440703313669" \ ---output-file .found - - -========== DISTANCE ATTITUDES ========== - -===== TEST PAIR 1 ===== - -Local Attitude: RA: 45.000, DE: -0.000, ROLL: -0.000 -Reference Attitude: RA: 317.190, DE: -75.585, ROLL: 49.943 - -Generator Command: -tools.generator --position \ ---orientation 45.00000000000001 -0.0 -0.0 \ - \ ---filename - -Distance Command: -./build/bin/found distance \ ---calibration-data .found \ ---reference-orientation "317.1896836000723 -75.5846266781195 49.942682662774814" \ - \ ---image - - -===== TEST PAIR 2 ===== - -Local Attitude: RA: 268.440, DE: 3.362, ROLL: 81.185 -Reference Attitude: RA: 269.790, DE: 57.374, ROLL: 159.302 - -Generator Command: -tools.generator --position \ ---orientation 268.4403942879669 3.3617435714570316 81.18495536399615 \ - \ ---filename - -Distance Command: -./build/bin/found distance \ ---calibration-data .found \ ---reference-orientation "269.79043976487196 57.37412986950127 159.30218566881078" \ - \ ---image - - -===== TEST PAIR 3 ===== - -Local Attitude: RA: 345.316, DE: 28.200, ROLL: 55.903 -Reference Attitude: RA: 309.672, DE: -12.132, ROLL: 128.372 - -Generator Command: -tools.generator --position \ ---orientation 345.315834301275 28.200499525315795 55.90281118134976 \ - \ ---filename - -Distance Command: -./build/bin/found distance \ ---calibration-data .found \ ---reference-orientation "309.6719718956234 -12.13229325630242 128.3723252988373" \ - \ ---image -``` \ No newline at end of file diff --git a/tools/attitude/__main__.py b/tools/attitude/__main__.py deleted file mode 100644 index 4b6a41a7..00000000 --- a/tools/attitude/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -import runpy - -# Alias to run actual source code -runpy.run_module("attitude.src", run_name="__main__", alter_sys=True) diff --git a/tools/attitude/environment.yml b/tools/attitude/environment.yml deleted file mode 100644 index a19b07f0..00000000 --- a/tools/attitude/environment.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: attitude_env -channels: - - conda-forge - - defaults -dependencies: - - python>=3.9 - - scipy - - pytest \ No newline at end of file diff --git a/tools/attitude/src/__init__.py b/tools/attitude/src/__init__.py deleted file mode 100644 index 8829a3bd..00000000 --- a/tools/attitude/src/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys -from pathlib import Path - -# Add the 'src' directory to sys.path -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) diff --git a/tools/attitude/src/__main__.py b/tools/attitude/src/__main__.py deleted file mode 100644 index 8517482e..00000000 --- a/tools/attitude/src/__main__.py +++ /dev/null @@ -1,198 +0,0 @@ -import argparse -import logging -from typing import Tuple, List, Optional - -from common.constants import ( - CALIBRATION_BANNER, - DISTANCE_BANNER, - CALIBRATION_HEADER, - LOCAL_HEADER, - REFERENCE_HEADER -) - -from transform.transform import Attitude, DCM - -logging.basicConfig(level=logging.DEBUG, format="[%(levelname)s]: %(message)s") - -def parse_args() -> Tuple[DCM, DCM, int]: - """Parse Arguments for the tool - - Args: - local_attitude (Optional[DCM]): The local attitude to use in the first test pair - calibration_attitude (DCM): The calibration rotation (that rotates from the reference to local orientation) - num_attitude_pairs (int): The number of test attitude pairs to generate - - Returns: - Tuple[Attitude, DCM, int]: The arguments - - Note: - We convert the local_attitude into a DCM for convenience - """ - - parser = argparse.ArgumentParser( - description="Generates orientations to use with calibration and distance modes of FOUND and generates commands to use with the orientations" - ) - - parser.add_argument( - "--local-attitude", - nargs=3, - type=float, - help="The orientation of FOUND (ra, de, roll) in degrees. Must be supplied if --use-local is used", - default=None - ) - parser.add_argument( - "--calibration-attitude", - nargs=3, - type=float, - help="The euler angles for the calibration rotation, if you want to reuse a particular calibration", - default=None - ) - parser.add_argument( - "--num-attitude-pairs", - type=int, - help="Number of test attitude pairs to generate. Must be at least 1", - default=1 - ) - args = parser.parse_args() - - # Step 1: Produce Calibration Rotation - if args.calibration_attitude is None: - calibration_attitude = DCM() - else: - calibration_attitude = Attitude(*args.calibration_attitude).to_dcm() - - return DCM() if args.local_attitude is None else Attitude(*args.local_attitude).to_dcm(), calibration_attitude, args.num_attitude_pairs - -def validate_arguments(local_attitude: Optional[DCM], - calibration_attitude: DCM, - num_attitude_pairs: int): - """Validates the arguments - - Args: - local_attitude (Optional[DCM]): The local attitude to use in the first test pair - calibration_attitude (DCM): The calibration rotation - num_attitude_pairs (int): The number of test attitude pairs to generate - """ - if num_attitude_pairs < 1: - raise RuntimeError("Must generate at least 1 test attitude pair") - -def produce_attitudes(local_attitude: Optional[DCM], - calibration_attitude: DCM, - num_attitude_pairs: int) -> Tuple[Tuple[DCM, DCM], List[Tuple[DCM, DCM]]]: - """Produces the attitudes to use with FOUND and tools.generator - - Args: - local_attitude (Optional[DCM]): The local attitude to use in the first test pair - calibration_attitude (DCM): The calibration rotation - num_attitude_pairs (int): The number of test attitude pairs to generate - - Returns: - Tuple[Tuple[Attitude, Attitude], List[Tuple[Attitude, Attitude]]]: A tuple of: - 1. The calibration attitudes to use (local, reference) - 2. A list of test attitudes to use (local, reference) - """ - # Step 0: Validate the arguments - validate_arguments(local_attitude, calibration_attitude, num_attitude_pairs) - - # Step 2: Produce the calibration bases - reference_attitude_cal = DCM() - local_attitude_cal = calibration_attitude.rotate(reference_attitude_cal) - calibration_attitudes = local_attitude_cal, reference_attitude_cal - - # Step 3: Produce the Test Attitudes - test_attitudes = list() - - # Step 3a: Handle the First Test Attitude Differently if we are given a local attitude - loc_attitude = local_attitude - ref_attitude = calibration_attitude.rotate(local_attitude, inverse=True) - test_attitudes.append((loc_attitude, ref_attitude)) - - # Step 3b: Handle the rest like as normal - for i in range(num_attitude_pairs - 1): - ref_attitude = DCM() - loc_attitude = calibration_attitude.rotate(ref_attitude) - - test_attitudes.append((loc_attitude, ref_attitude)) - - # Return the result - return calibration_attitudes, test_attitudes - -def output_result(calibration_attitude: DCM, - calibration_attitudes: Tuple[DCM, DCM], - test_attitudes: List[Tuple[DCM, DCM]]): - """Outputs the results - - Args: - calibration_attitude (DCM): The calibration rotation - calibration_attitudes (Tuple[DCM, DCM]): The attitudes to use for calibration - test_attitudes (List[Tuple[DCM, DCM]]): The attitudes to use for distance - """ - # Define constants - - LF = "\n" - SECTION_SEPERATOR = LF + LF - SLASHLF = "\\" + LF - - # Define output functions - - def print_attitude(banner: str, attitude: Attitude, end=LF): - if end is None: - print(f"{banner:25}", f"RA: {attitude.ra:>7.3f}, DE: {attitude.de:>7.3f}, ROLL: {attitude.roll:>7.3f}") - else: - print(f"{banner:25}", f"RA: {attitude.ra:>7.3f}, DE: {attitude.de:>7.3f}, ROLL: {attitude.roll:>7.3f}", end) - - def print_attitudes(loc_attitude: Attitude, ref_attitude: Attitude): - print_attitude(LOCAL_HEADER, loc_attitude, None) - print_attitude(REFERENCE_HEADER, ref_attitude) - - def print_distance_commands(loc_attitude: Attitude, ref_attitude: Attitude): - generator_cmd = f"Generator Command:{LF}tools.generator --position {SLASHLF}" \ - + f"--orientation {loc_attitude.to_generator_format()} {SLASHLF}" \ - + f" {SLASHLF}" \ - + f"--filename " - distance_cmd = f"Distance Command:{LF}./build/bin/found distance {SLASHLF}" \ - + f"--calibration-data .found {SLASHLF}" \ - + f"--reference-orientation {ref_attitude.to_found_format()} {SLASHLF}" \ - + f" {SLASHLF}" \ - + f"--image " - - print(generator_cmd, LF) - print(distance_cmd) - - # Convert everything to Attitude - calibration_attitude = calibration_attitude.to_attitude() - calibration_attitudes = calibration_attitudes[0].to_attitude(), \ - calibration_attitudes[1].to_attitude() - for i in range(len(test_attitudes)): - loc_attitude, ref_attitude = test_attitudes[i] - test_attitudes[i] = loc_attitude.to_attitude(), ref_attitude.to_attitude() - - # Define Calibration Command - CALIBRATION_COMMAND = f"Calibration Command:{LF}./build/bin/found calibration {SLASHLF}" \ - + f"--reference-orientation {calibration_attitudes[1].to_found_format()} {SLASHLF}" \ - + f"--local-orientation {calibration_attitudes[0].to_found_format()} {SLASHLF}" \ - + f"--output-file .found" - - # Step 4: Output - - # Step 4a: Print Calibration Step - print(f"{LF}{CALIBRATION_BANNER}", LF) - print_attitude(CALIBRATION_HEADER, calibration_attitude) - print_attitudes(*calibration_attitudes) - print(CALIBRATION_COMMAND, SECTION_SEPERATOR) - - # Step 4b: Print all other Steps - print(DISTANCE_BANNER, LF) - for i, (loc_attitude, ref_attitude) in enumerate(test_attitudes): - print(f"===== TEST PAIR {i + 1} =====", LF) - print_attitudes(loc_attitude, ref_attitude) - print_distance_commands(loc_attitude, ref_attitude) - if i != len(test_attitudes) - 1: - print(SECTION_SEPERATOR) - else: - print() - -if __name__ == "__main__": - arguments = parse_args() - attitudes = produce_attitudes(*arguments) - output_result(arguments[-2], *attitudes) \ No newline at end of file diff --git a/tools/attitude/src/common/constants.py b/tools/attitude/src/common/constants.py deleted file mode 100644 index b7b34c78..00000000 --- a/tools/attitude/src/common/constants.py +++ /dev/null @@ -1,12 +0,0 @@ -# Rotational Constants -ROTATION_ORDER: str = 'ZYX' # Rotation Order -ANGLE_NORM = 360 # Angle Norm - -# Output Banners -CALIBRATION_BANNER = "========== CALIBRATION ATTITUDES ==========" # Calibration Banner -DISTANCE_BANNER = "========== DISTANCE ATTITUDES ==========" # Distance Banner - -# Output Headers -CALIBRATION_HEADER = "Calibration Attitude: " # Calibration Attitude Header -LOCAL_HEADER = "Local Attitude: " # Local Attitude Header -REFERENCE_HEADER = "Reference Attitude: " # Reference Attitude Header \ No newline at end of file diff --git a/tools/attitude/src/transform/transform.py b/tools/attitude/src/transform/transform.py deleted file mode 100644 index 56309bc1..00000000 --- a/tools/attitude/src/transform/transform.py +++ /dev/null @@ -1,97 +0,0 @@ -from scipy.spatial.transform import Rotation -from common.constants import ROTATION_ORDER, ANGLE_NORM - -class Attitude: - """Represents an attitude in the celestial sphere (using modified Euler Angles) - - Note: - We use degrees here - """ - - def __init__(self, ra: float, de: float, roll: float): # pyright: ignore[reportRedeclaration] - """Creates an Attitude object - - Args: - right_ascension (float): The right ascension of the attitude - declination (float): The declination of the attitude - roll (float): The roll of the attitude - """ - self.ra = ra - self.de = de - self.roll = roll - - while self.ra > ANGLE_NORM: - self.ra -= ANGLE_NORM - while self.ra < 0: - self.ra += ANGLE_NORM - - while self.de > ANGLE_NORM / 2: - self.de -= ANGLE_NORM - while self.de < -ANGLE_NORM / 2: - self.de += ANGLE_NORM - - while self.roll > 0: - self.roll -= ANGLE_NORM - while self.roll < 0: - self.roll += ANGLE_NORM - - def to_dcm(self): - """Converts the Attitude to a DCM - - Returns: - DCM: The resulting DCM - """ - return DCM(Rotation.from_euler(ROTATION_ORDER, [self.ra, -self.de, -self.roll], degrees=True)) - - def to_generator_format(self) -> str: - """Provides the string representation for the tools.generator CLI - - Returns: - str: The string to pass to the tools.generator CLI - """ - return f"{self.ra} {self.de} {self.roll}" - - def to_found_format(self) -> str: - """Provides the string representation for the FOUND CLI - - Returns: - str: The string to pass to the FOUND CLI - """ - return f"\"{self.to_generator_format()}\"" - -class DCM: - """Represents a Rotation/Orthonormal Basis - """ - - def __init__(self, rotation: Rotation = None): - """Constructs a DCM from a scipy Rotation - - Args: - rotation (Rotation, optional): The rotation to use. Defaults to Rotation.random(). - """ - if rotation is None: - rotation = Rotation.random() - self.rotation = rotation - - def rotate(self, other, inverse: bool = False): - """Rotates another DCM - - Args: - other (DCM): The other DCM to rotate - inverse (bool, optional): Whether to apply the inverse rotation. Defaults to False. - - Returns: - DCM: The resulting DCM - """ - if not inverse: - return DCM(self.rotation * other.rotation) - return DCM(self.rotation.inv() * other.rotation) - - def to_attitude(self) -> Attitude: - """Represents the DCM as an Attitude basis - - Returns: - Attitude: The attitude represented by this basis - """ - ra, de, roll = self.rotation.as_euler(ROTATION_ORDER, degrees=True) - return Attitude(ra, -de, -roll) \ No newline at end of file diff --git a/tools/attitude/test/conftest.py b/tools/attitude/test/conftest.py deleted file mode 100644 index 0698f2d1..00000000 --- a/tools/attitude/test/conftest.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys -from pathlib import Path - -# Add the 'src' directory to sys.path -sys.path.insert(0, str(Path(__file__).resolve().parents[1])) diff --git a/tools/attitude/test/integration/test_integration.py b/tools/attitude/test/integration/test_integration.py deleted file mode 100644 index 50587af6..00000000 --- a/tools/attitude/test/integration/test_integration.py +++ /dev/null @@ -1,139 +0,0 @@ -import unittest -import re -import io -from contextlib import redirect_stdout -from scipy.spatial.transform import Rotation -from typing import Optional - -from src.transform.transform import DCM, Attitude -from src.common.constants import ( - ROTATION_ORDER, - CALIBRATION_BANNER, - DISTANCE_BANNER, - CALIBRATION_HEADER, - LOCAL_HEADER, - REFERENCE_HEADER -) - -from src.__main__ import produce_attitudes, output_result - -RE_FLOAT = r"[+-]?\d+(?:\.\d+)?" -RE_WHITESPACE = r"\s+" - -DEFAULT_NUM_POINTS = 50 - -class Arguments: - def __init__(self, - local_attitude: Optional[DCM], - calibration_attitude: DCM, - num_attitude_pairs: int = DEFAULT_NUM_POINTS): - self.local_attitude = local_attitude - self.calibration_attitude = calibration_attitude - self.num_attitude_pairs = num_attitude_pairs - def to_tuple(self): - return self.local_attitude, self.calibration_attitude, self.num_attitude_pairs - -class IntegrationTest(unittest.TestCase): - - def assertAttitudesAlmostEqual(self, att1: Attitude, att2: Attitude): - self.assertAlmostEqual(att1.ra, att2.ra, 2) - self.assertAlmostEqual(att1.de, att2.de, 2) - self.assertAlmostEqual(att1.roll, att2.roll, 2) - - def assertDCMAlmostEqual(self, dcm1: DCM, dcm2: DCM): - self.assertAttitudesAlmostEqual(dcm1.to_attitude(), dcm2.to_attitude()) - - def assertAttitudeOffsetRA(self, dcm1: DCM, dcm2: DCM, offset: float): - att1, att2 = dcm1.to_attitude(), dcm2.to_attitude() - - mod2 = Attitude(att2.ra + offset, att2.de, att2.roll) - - self.assertAttitudesAlmostEqual(att1, mod2) - - def assertAttitudeOffsetGeneral(self, dcm1: DCM, dcm2: DCM, offset: DCM): - self.assertDCMAlmostEqual(dcm1, offset.rotate(dcm2)) - - def test_no_rotation(self): - arguments = Arguments(DCM(), DCM(Rotation.from_euler(ROTATION_ORDER, [0, 0, 0], degrees=True))) - - calibration, tests = produce_attitudes(*arguments.to_tuple()) - - self.assertDCMAlmostEqual(*calibration) - - for local, reference in tests: - self.assertDCMAlmostEqual(local, reference) - - def test_simple_rotation(self): - arguments = Arguments(DCM(), DCM(Rotation.from_euler(ROTATION_ORDER, [135, 0, 0], degrees=True))) - - calibration, tests = produce_attitudes(*arguments.to_tuple()) - - self.assertAttitudeOffsetRA(*calibration, 135) - - for local, reference in tests: - self.assertAttitudeOffsetRA(local, reference, 135) - - def test_rotation_general(self): - arguments = Arguments(DCM(), DCM()) - - calibration, tests = produce_attitudes(*arguments.to_tuple()) - - self.assertAttitudeOffsetGeneral(*calibration, arguments.calibration_attitude) - - for local, reference in tests: - self.assertAttitudeOffsetGeneral(local, reference, arguments.calibration_attitude) - - def test_rotation_general_with_local_attitude(self): - local_attitude = DCM() - arguments = Arguments(local_attitude, DCM()) - - calibration, tests = produce_attitudes(*arguments.to_tuple()) - - self.assertAttitudeOffsetGeneral(*calibration, arguments.calibration_attitude) - - for local, reference in tests: - self.assertAttitudeOffsetGeneral(local, reference, arguments.calibration_attitude) - - self.assertDCMAlmostEqual(local_attitude, tests[0][0]) - - def test_print_output(self): - # Here, we don't actually care about the content of these - calibration_attitude = DCM() - calibration_attitudes = DCM(), DCM() - test_attitudes = [(DCM(), DCM()) for _ in range(DEFAULT_NUM_POINTS)] - - attitude_list = [calibration_attitude, *calibration_attitudes, calibration_attitudes[1], calibration_attitudes[0]] - for el in test_attitudes: - attitude_list.extend(el) - attitude_list.extend(el) - - common_matcher = rf"{RE_WHITESPACE}RA:{RE_WHITESPACE}({RE_FLOAT}),?{RE_WHITESPACE}DE:{RE_WHITESPACE}({RE_FLOAT}),?{RE_WHITESPACE}ROLL:{RE_WHITESPACE}({RE_FLOAT})" - number_list_matcher = rf"({RE_FLOAT}) ({RE_FLOAT}) ({RE_FLOAT})" - calibration_matcher = re.compile(rf"^{CALIBRATION_HEADER}{common_matcher}") - ref_ori_matcher = re.compile(rf'^--reference-orientation "{number_list_matcher}"') - local_ori_matcher = re.compile(rf'^--local-orientation "{number_list_matcher}"') - pair_matcher = re.compile(rf"^{LOCAL_HEADER}{common_matcher}"), re.compile(rf"^{REFERENCE_HEADER}{common_matcher}") - ori_matcher = re.compile(rf'^--orientation {number_list_matcher}') - - matcher_list = [calibration_matcher, *pair_matcher, ref_ori_matcher, local_ori_matcher] - for _ in range(DEFAULT_NUM_POINTS): - matcher_list.extend(pair_matcher) - matcher_list.append(ori_matcher) - matcher_list.append(ref_ori_matcher) - - f = io.StringIO() - with redirect_stdout(f): - output_result(calibration_attitude, calibration_attitudes, test_attitudes) - - lines = f.getvalue().splitlines() - - i = 0 - for line in lines: - match = matcher_list[i].match(line) - if match: - self.assertDCMAlmostEqual(attitude_list[i], Attitude(*[float(num) for num in match.groups()]).to_dcm()) - i += 1 - if i == len(matcher_list): - break - - self.assertEqual(i, len(matcher_list)) \ No newline at end of file diff --git a/tools/attitude/test/transform/test_transform.py b/tools/attitude/test/transform/test_transform.py deleted file mode 100644 index 382e2c54..00000000 --- a/tools/attitude/test/transform/test_transform.py +++ /dev/null @@ -1,89 +0,0 @@ -import unittest -from scipy.spatial.transform import Rotation - -from src.transform.transform import Attitude, DCM -from src.common.constants import ROTATION_ORDER - -class TransformTest(unittest.TestCase): - - def test_attitude_is_normalized_ra_above(self): - attitude = Attitude(400, 40, -92) - - self.assertAlmostEqual(40, attitude.ra) - - def test_attitude_is_normalized_ra_below(self): - attitude = Attitude(-40, 40, -92) - - self.assertAlmostEqual(320, attitude.ra) - - def test_attitude_is_normalized_de_above(self): - attitude = Attitude(90, 270, 0) - - self.assertAlmostEqual(-90, attitude.de) - - def test_attitude_is_normalized_de_below(self): - attitude = Attitude(90, -180 - 45, 0) - - self.assertAlmostEqual(135, attitude.de) - - def test_attitude_is_normalized_roll_above(self): - attitude = Attitude(0, 90, 360 + 90) - - self.assertAlmostEqual(90, attitude.roll) - - def test_attitude_is_normalized_roll_below(self): - attitude = Attitude(0, 90, -90) - - self.assertAlmostEqual(270, attitude.roll) - - def test_attitude_to_euler(self): - attitude = Attitude(48, -26, 278) - - angles = attitude.to_dcm().rotation.as_euler(ROTATION_ORDER, degrees=True) - actual = Attitude(angles[0], -angles[1], -angles[2]) - - self.assertAlmostEqual(attitude.ra, actual.ra) - self.assertAlmostEqual(attitude.de, actual.de) - self.assertAlmostEqual(attitude.roll, actual.roll) - - def test_dcm_rotate_simple(self): - dcm = DCM(Rotation.from_euler(ROTATION_ORDER, [90, 0, 0], degrees=True)) - - input_angles = [86, -26, 92] - input = DCM(Rotation.from_euler(ROTATION_ORDER, input_angles, degrees=True)) - - actual = dcm.rotate(input).rotation.as_euler(ROTATION_ORDER, degrees=True) - - self.assertAlmostEqual(input_angles[0] + 90, actual[0]) - self.assertAlmostEqual(input_angles[1], actual[1]) - self.assertAlmostEqual(input_angles[2], actual[2]) - - def test_dcm_rotate_simple_inverse(self): - dcm = DCM(Rotation.from_euler(ROTATION_ORDER, [90, 0, 0], degrees=True)) - - input_angles = [86, -26, 92] - input = DCM(Rotation.from_euler(ROTATION_ORDER, input_angles, degrees=True)) - - actual = dcm.rotate(input, inverse=True).rotation.as_euler(ROTATION_ORDER, degrees=True) - - self.assertAlmostEqual(input_angles[0] - 90, actual[0]) - self.assertAlmostEqual(input_angles[1], actual[1]) - self.assertAlmostEqual(input_angles[2], actual[2]) - - def test_dcm_rotate_general(self): - for _ in range(10): - rotation_angles = Rotation.random() - input_angles = Rotation.random() - - rotation = DCM(rotation_angles) - input = DCM(input_angles) - - expected_angles = list((rotation_angles * input_angles).as_euler(ROTATION_ORDER, degrees=True)) - actual_angles = list(rotation.rotate(input).rotation.as_euler(ROTATION_ORDER, degrees=True)) - - self.assertListEqual(expected_angles, actual_angles) - - expected_angles_inv = list((rotation_angles.inv() * input_angles).as_euler(ROTATION_ORDER, degrees=True)) - actual_angles_inv = list(rotation.rotate(input, inverse=True).rotation.as_euler(ROTATION_ORDER, degrees=True)) - - self.assertListEqual(expected_angles_inv, actual_angles_inv) \ No newline at end of file diff --git a/tools/generator/README.md b/tools/generator/README.md deleted file mode 100644 index e8a1d286..00000000 --- a/tools/generator/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Generator - -The `generator` tool generates artificial images of earth using a position and orientation. - -Required Flags: -1. `--position`: The position of the object, in meters (space seperated) -2. `--orientation`: The orientation of the object, in DE, RA, ROLL (space seperated) -Optional Flags -1. `--filename`: The name of the output file (.png) -2. For the rest, run `tools.generator --help` - -Example Usage (from `found` root): -```bash - -python3 -m tools.generator --position 10378137 0 0 --orientation 140 0 0 --focal-length 85e-3 --pixel-size 20e-6 --x-resolution 512 --y-resolution 512 --filename example_earth.png - -``` - -![Example Image of Earth](../../test/common/assets/example_earth1.png) \ No newline at end of file diff --git a/tools/generator/__main__.py b/tools/generator/__main__.py deleted file mode 100644 index d16d1195..00000000 --- a/tools/generator/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -import runpy - -# Alias to run actual source code -runpy.run_module("generator.src", run_name="__main__", alter_sys=True) diff --git a/tools/generator/environment.yml b/tools/generator/environment.yml deleted file mode 100644 index 5be18ced..00000000 --- a/tools/generator/environment.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: generator_env -channels: - - conda-forge - - defaults -dependencies: - - python>=3.9 - - numpy - - pycairo - - pytest \ No newline at end of file diff --git a/tools/generator/src/__init__.py b/tools/generator/src/__init__.py deleted file mode 100644 index 8829a3bd..00000000 --- a/tools/generator/src/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys -from pathlib import Path - -# Add the 'src' directory to sys.path -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) diff --git a/tools/generator/src/__main__.py b/tools/generator/src/__main__.py deleted file mode 100644 index 090e4bb6..00000000 --- a/tools/generator/src/__main__.py +++ /dev/null @@ -1,266 +0,0 @@ -import argparse -import logging -from pathlib import Path -from typing import List, Tuple - -logging.basicConfig(level=logging.DEBUG, format="[%(levelname)s]: %(message)s") - -from common.constants import ( - DEFAULT_FOCAL_LEN, - DEFAULT_RESOLUTION, - DEFAULT_PIXEL_SIZE, - DEFAULT_LEO_THRESHOLD, - EARTH_RADIUS, - NUM_EARTH_POINTS, -) -from spatial.coordinate import Attitude, Vector -from spatial.camera import Camera -from curve.spherical import SphericalCurveProvider -from image.printer import Printer - - -def parse_args() -> Tuple[Vector, Attitude, float, float, float, float, float, str]: - """Parses out the arguments for this program - - Args: - position (List[float]): The position of the satellite (meters, [x, y, z]). - orientation (List[float]): The attitude of the satellite (degrees, [ra, de, roll]). - leo-threshold (float, Optional): The lowest expected altitude from the surface of Earth (meters). - focal-length (float, Optional): The focal length of the camera (meters). Defaults to 8.5e-3 meters. - pixel-length (float, Optional): The physical length of a pixel (meters). Defaults to 20e-6 meters. - x-resolution (float, Optional): The x resolution of the camera (pixels). Defaults to 6000 pixels. - y-resolution (float, Optional): The y resolution of the camera (pixels). Defaults to 6000 pixels. - filename (str, Optional): The filename (including path) of the output image (.png). Defaults to a name that describes position and orientation, outputted directly into the repository (found) folder. - - Returns: - Tuple[Vector, Attitude, float, float, float, float, float, str]: The arguments for this program - """ - parser = argparse.ArgumentParser( - description="Generates artificial images of Earth" - ) - parser.add_argument( - "--position", - nargs=3, - type=float, - help="The position of the satellite (x, y, z)", - required=True, - ) - parser.add_argument( - "--orientation", - nargs=3, - type=float, - help="The orientation of the satellite (ra, de, roll)", - required=True, - ) - parser.add_argument( - "--leo-threshold", - type=float, - default=DEFAULT_LEO_THRESHOLD, - help="The lowest orbit altitude above earth's surface (in meters)", - ) - parser.add_argument( - "--focal-length", - type=float, - default=DEFAULT_FOCAL_LEN, - help="The focal length of the camera (in meters)", - ) - parser.add_argument( - "--pixel-size", - type=float, - default=DEFAULT_PIXEL_SIZE, - help="The pixel size (in m^2)", - ) - parser.add_argument( - "--x-resolution", - type=int, - default=DEFAULT_RESOLUTION, - help="The x resolution of the camera (in pixels)", - ) - parser.add_argument( - "--y-resolution", - type=int, - default=DEFAULT_RESOLUTION, - help="The y resolution of the camera (in pixels)", - ) - parser.add_argument("--filename", default=None, help="The output file name") - args = parser.parse_args() - - position, orientation = Vector(*args.position), Attitude( - *args.orientation, radians=False - ) - - return ( - position, - orientation, - args.leo_threshold, - args.focal_length, - args.pixel_size, - args.x_resolution, - args.y_resolution, - ( - args.filename - if args.filename - else f"{Path(__file__).resolve().parents[3]}/{position},{orientation}" - ), - ) - - -def generate_points( - position: Vector, - orientation: Attitude, - leo_threshold: float, - focal_length: float, - pixel_size: float, - x_resolution: int, - y_resolution: int, - num_points: int = NUM_EARTH_POINTS, -) -> Tuple[Camera, List[Vector]]: - """Generates the edge points of earth as seen in the camera given by all parameters - - Args: - position (Vector): The position of the camera - orientation (Attitude): The orientation of the camera - leo_threshold (float): The low threshold for orbit altitude - focal_length (float): The camera focal length - pixel_size (float): The physical length of a pixel - x_resolution (int): The number of pixels in the image along the x direction - y_resolution (int): The number of pixels in the image along the y direction - - Returns: - Tuple[Camera, List[Vector]]: The camera and the edge points generated on its image - - Note: - The returned edge points are given with respect to the camera 2d coordinate system, - not the graphics coordinate system (the origin is in the center of the image, pointing - up and to the left) - """ - # Step 1: Figure out if the parameters are okay - if position.norm < EARTH_RADIUS: - logging.error(f"The position {position} is invalid (not above earth)") - exit(1) - if position.norm < leo_threshold: - logging.warning(f"The position {position} is below LEO") - - # Step 2: Figure out camera coordinate system with respect to celestial coordinate system - camera = Camera( - position, - orientation, - focal_length, - pixel_size, - x_resolution, - y_resolution, - ) - - # Step 3: Get curve points from earth wrt celestial coordinate system - spherical_curve_provider = SphericalCurveProvider(position) - center, points = spherical_curve_provider.generate_points(num_points=num_points) - - # Step 3E: Calculate if its possible that a head on image of Earth may not - # be feasible - if ( - camera.pixel_length - * max(camera.x_resolution, camera.y_resolution) - / camera.focal_length - < spherical_curve_provider.radius.norm - / (position - spherical_curve_provider.center).norm - ): - logging.warning( - f"The camera may not capture Earth's edge ([sensor width/focal length={camera.pixel_length * max(camera.x_resolution, camera.y_resolution) / camera.focal_length:.3f}] < [edge radius/center distance={spherical_curve_provider.radius.norm / (position - spherical_curve_provider.center).norm:.3f}])" - ) - - # Step 4: Transform points into camera coordinate system - camera_points = camera.to_camera_space(points) - camera_center = camera.to_camera_space({center}) - - # Step 5: Project Points into Camera - image_center, *image_points = camera.spatial_to_camera( - camera_center, *camera_points - ) - - if image_center == None: - logging.error( - "The produced image does not capture Earth's edge. Skipping image output." - ) - exit(1) - count = 0 - for pt in image_points: - if camera.in_camera(pt): - count += 1 - if count == 0: - logging.error( - "The produced image does not capture Earth's edge. Skipping image output." - ) - exit(1) - if len(image_points) != len(camera_points): - logging.warning( - f"{(len(camera_points)-len(image_points)) / len(camera_points) * 100:.2f}% of the horizon is behind the camera" - ) - logging.info( - f"{count / len(camera_points) * 100:.2f}% of the horizon is in the camera" - ) - - return ( - camera, - image_points, - ) - - -def print_image( - camera: Camera, - image_points: List[Vector], - filename: str, -): - """Generates an image - - Args: - camera (Camera): The camera that took the image - image_points (List[Vector]): The edge points that go onto the image - filename (str): The file to save the image to - - Precondition: - image_points is given with respect to the camera 2d axes - """ - # Step 6: Draw and Save Image - printer = Printer(camera) - printer.generate_image(image_points) - printer.save_image(filename) - - -if __name__ == "__main__": - """Runs the generator script to get an image of earth. - We use the celestial coordinate system and assume Earth is a sphere - - pythom -m generator --focal_length [focal_length] --x_resolution [x_resolution] --y_resolution [y_resolution] - - Args: - position (List[float]): The position of the satellite (meters, [x, y, z]). - orientation (List[float]): The attitude of the satellite (degrees, [ra, de, roll]). - leo-threshold (float, Optional): The lowest expected altitude from the surface of Earth (meters). - focal-length (float, Optional): The focal length of the camera (meters). Defaults to 8.5e-3 meters. - pixel-length (float, Optional): The physical length of a pixel (meters). Defaults to 20e-6 meters. - x-resolution (float, Optional): The x resolution of the camera (pixels). Defaults to 6000 pixels. - y-resolution (float, Optional): The y resolution of the camera (pixels). Defaults to 6000 pixels. - filename (str, Optional): The filename (including path) of the output image. Defaults to a name that describes position and orientation, outputted directly into the repository (found) folder. - """ - ( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - filename, - ) = parse_args() - - camera, image_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - ) - - print_image(camera, image_points, filename) diff --git a/tools/generator/src/common/constants.py b/tools/generator/src/common/constants.py deleted file mode 100644 index 85f37436..00000000 --- a/tools/generator/src/common/constants.py +++ /dev/null @@ -1,13 +0,0 @@ -## CAMERA PARAMETERS -DEFAULT_FOCAL_LEN = 0.085 # Default focal length (m) -DEFAULT_RESOLUTION = 6000 # Default resolution (pixels) -DEFAULT_PIXEL_SIZE = 20e-6 # Default pixel size (m) - -## LEO THRESHOLD -DEFAULT_LEO_THRESHOLD = 2000000 # Lowest Low-Earth Orbit altitude (m) - -## EARTH CONSTANTS -EARTH_RADIUS = 6378137 # Average Earth Radius (m) - -## Software Parameters -NUM_EARTH_POINTS = 360 * 10 # Number of Earth Points to generate diff --git a/tools/generator/src/common/eval.py b/tools/generator/src/common/eval.py deleted file mode 100644 index 02bf4959..00000000 --- a/tools/generator/src/common/eval.py +++ /dev/null @@ -1,12 +0,0 @@ -def float_equals(num1: float, num2: float, tolerance: float = 1e-3) -> bool: - """See's if a float is equal to another float - - Args: - num1 (float): One float to evaluate - num2 (float): The other float to evaluate against - tolerance (float, optional): The maximum difference. Defaults to 1e-3. - - Returns: - bool: True iff num1 and num2 are roughly the same (their difference is less than tolerance) - """ - return abs(num1 - num2) < tolerance diff --git a/tools/generator/src/curve/curve.py b/tools/generator/src/curve/curve.py deleted file mode 100644 index c71c9025..00000000 --- a/tools/generator/src/curve/curve.py +++ /dev/null @@ -1,34 +0,0 @@ -from abc import ABC, abstractmethod - -from common.constants import NUM_EARTH_POINTS -from spatial.coordinate import Vector - -from typing import Tuple, Iterable - - -class CurveProvider(ABC): - """Provides the edge points in the celestial coordinate system as visible from a satelite at some position""" - - @abstractmethod - def __init__(self, position: Vector): - """Initializes this curve provider - - Args: - position (Vector): The position vector that represents where the satellite is with relation to Earth, in the celestial coordinate system - """ - pass - - @abstractmethod - def generate_points( - self, num_points: int = NUM_EARTH_POINTS - ) -> Tuple[Vector, Iterable[Vector]]: - """Generates the visible edge points of earth as can be possibly observed - from a spacecraft, in terms of the celestial coordinate system - - Args: - num_points (int, optional): The number of points to generate. Defaults to NUM_EARTH_POINTS. - - Returns: - Tuple[Vector, Iterable[Vector]]: The center of the points, and the edge points. - """ - pass diff --git a/tools/generator/src/curve/spherical.py b/tools/generator/src/curve/spherical.py deleted file mode 100644 index 32638ab9..00000000 --- a/tools/generator/src/curve/spherical.py +++ /dev/null @@ -1,42 +0,0 @@ -import numpy as np - -from spatial.coordinate import Vector -from common.constants import EARTH_RADIUS, NUM_EARTH_POINTS -from common.eval import float_equals -from .curve import CurveProvider - - -class SphericalCurveProvider(CurveProvider): - """ - Provides the edge points of earth by representing earth as a sphere - - Parents: - CurveProvider: The base abstract class that defines a curve provider - """ - - def __init__(self, position: Vector): - # Step 1: Get the center vector - distance = position.norm - alpha = np.arcsin(EARTH_RADIUS / distance) - tangent_len = np.sqrt(distance * distance - EARTH_RADIUS * EARTH_RADIUS) - center_len = distance - tangent_len * np.cos(alpha) - self.center = position.normalize() * center_len - - # Step 2: Get the radius vector - # Use the same vector actually to just manipulate it into an orthogonal one - radius_len = tangent_len * np.sin(alpha) - if not float_equals(0.0, position[0]): - self.radius = Vector(-(position[1] + position[2]) / position[0], 1, 1) - elif not float_equals(0.0, position[1]): - self.radius = Vector(1, -(position[0] + position[2]) / position[1], 1) - else: - self.radius = Vector(1, 1, -(position[0] + position[1]) / position[2]) - self.radius = self.radius.normalize() * radius_len - self.radius2 = self.center.cross(self.radius).normalize() * radius_len - - def generate_points(self, num_points: int = NUM_EARTH_POINTS): - datapoints = np.linspace(0, 2 * np.pi, num_points) - return self.center, [ - self.center + self.radius * np.sin(t) + self.radius2 * np.cos(t) - for t in datapoints - ] diff --git a/tools/generator/src/image/printer.py b/tools/generator/src/image/printer.py deleted file mode 100644 index c4fae735..00000000 --- a/tools/generator/src/image/printer.py +++ /dev/null @@ -1,96 +0,0 @@ -import cairo - -from common.eval import float_equals -from spatial.coordinate import Vector -from spatial.camera import Camera - -from typing import Iterable - - -class Printer: - """A Printer prints an image of Earth""" - - def __init__(self, camera: Camera): - """Creates this printer - - Args: - camera (Camera): The camera that took the image - """ - self.camera = camera - self.width, self.height = camera.x_resolution, camera.y_resolution - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height) - self.context = cairo.Context(self.surface) - - def _start_drawing(self): - """Sets up the printer to start drawing""" - # Set background color (black) - self.context.set_source_rgb(0, 0, 0) - self.context.paint() - - def _transform_point(self, *points: Vector) -> Vector: - """Transforms a point from the camera 2d axis to the - graphics 2d axis - - Args: - points (Vector): The points to transform - - Returns: - Vector: The transformed points - """ - result = [] - reference_point = Vector( - self.camera.x_resolution / 2, self.camera.y_resolution / 2 - ) - for point in points: - result.append(reference_point - point) - return result - - def _draw_earth(self, curve_points: Iterable[Vector]): - """Draws Earth given its curve points - - Args: - curve_points (Iterable[Vector]): The curve points of Earth - that are observed as its edge - """ - # Set fill color (RGB: Blue) - self.context.set_source_rgb(0, 50, 100) - curve_points = self._transform_point(*curve_points) - - # Move to the first point - self.context.move_to(*curve_points[0]) - - for point in curve_points[1:]: - self.context.line_to(*point) - - # Close the shape and fill it - self.context.close_path() - self.context.fill() - - def _stop_drawing(self): - """Stops drawing""" - pass - - def generate_image(self, curve_points: Iterable[Vector]): - """Generates an image of earth - - Args: - curve_points (Iterable[Vector]): The edge points of earth - to use - """ - self._start_drawing() - self._draw_earth(curve_points) - self._stop_drawing() - - def save_image(self, filename: str): - """Saves the generated image of earth - - Args: - filename (str): The file to save it into - - Precondition - self.generate_image(...) should've been - called before this - """ - if '.png' in filename: - filename = filename[:filename.index('.png')] - self.surface.write_to_png(f"{filename}.png") diff --git a/tools/generator/src/spatial/camera.py b/tools/generator/src/spatial/camera.py deleted file mode 100644 index c17b7280..00000000 --- a/tools/generator/src/spatial/camera.py +++ /dev/null @@ -1,103 +0,0 @@ -from .coordinate import CoordinateSystem, Vector, Attitude - -from typing import Iterable, Union, Tuple - - -class Camera(CoordinateSystem): - """The camera represents a camera, complete with - coordinate system information - - Parents: - CoordinateSystem: Represents another 3D coordinate system - """ - - def __init__( - self, - position: Vector, - orientation: Attitude, - focal_length: float, - pixel_size: float, - x_resolution: float, - y_resolution: float, - ): - """Creates a Camera - - Args: - position (Vector): The position of this camera in space - orientation (Attitude): The orientation of this camera - focal_length (float): The focal length of this camera - pixel_size (float): The pixel size of this camera - x_resolution (float): The x resolution of this camera - y_resolution (float): The y resolution of this camera - """ - super().__init__(position, orientation) - self.focal_length = focal_length - self.pixel_length = pixel_size - self.x_resolution = x_resolution - self.y_resolution = y_resolution - - def to_camera_space(self, points: Iterable[Vector]) -> Iterable[Vector]: - """Transforms vectors in the reference to the camera's frame - - Args: - points (Iterable[Vector]): The points to transform - - Returns: - Iterable[Vector]: The transformed points - """ - return self.to_coordinate_system(points) - - def in_camera(self, point: Vector) -> bool: - """Evaluates if a 2D point is in the camera - sensor - - Args: - point (Vector): The point to evaluate - - Raises: - ValueError: If point is not a 2D point - - Returns: - bool: True iff it is in the sensor - """ - if point.dimension != 2: - raise ValueError( - f"Expected vector to have dimension 2, but was {point.dimension}" - ) - x_lim = self.x_resolution / 2 - y_lim = self.y_resolution / 2 - return (-x_lim <= point[0] and point[0] <= x_lim) and ( - -y_lim <= point[1] and point[1] <= y_lim - ) - - def spatial_to_camera( - self, *points: Iterable[Vector] - ) -> Union[Vector, Tuple[None], Tuple[Vector]]: - """Converts 3D points in the camera coordinate system to - 2D points on its sensor. Does not perform pixel truncation. - - Args: - points: The points to transform - - Returns: - Union[Vector, Tuple[None], Tuple[Vector]]: A single point if a single - point was passed, a collection of None or a Tuple of Vectors corresponding - to each point transformed - - Postcondition: - Ignores points behind the sensor (are not included in the return value, and - are dropped entirely). - Does not ignore points that are not on the sensor but still in front of it. - """ - result = [] - for point in points: - if point[0] < self.focal_length: - continue - factor = self.focal_length / point[0] / self.pixel_length - image_point = Vector(factor * point[1], factor * point[2]) - result.append(image_point) - if not result: - return [None] * len(points) - elif len(result) == 1: - return result[0] - return result diff --git a/tools/generator/src/spatial/coordinate.py b/tools/generator/src/spatial/coordinate.py deleted file mode 100644 index 4f385438..00000000 --- a/tools/generator/src/spatial/coordinate.py +++ /dev/null @@ -1,245 +0,0 @@ -import numpy as np -from common.eval import float_equals -from typing import Iterable, Iterator - - -class Attitude: - """Represents an attitude in the celestial sphere (using modified Euler Angles)""" - - def __init__( - self, - right_ascension: float, - declination: float, - roll: float, - radians: bool = True, - ): - """Creates an Attitude object - - Args: - right_ascension (float): The right ascension of the attitude - declination (float): The declination of the attitude - roll (float): The roll of the attitude - radians (bool, optional): Whether the 3 parameters are all specified as radians. Defaults to True. - """ - converter = lambda x: np.deg2rad(x) if not radians else x - self.right_ascension = converter(right_ascension) - self.declination = converter(declination) - self.roll = converter(roll) - - def __str__(self) -> str: - """Returns the string representation of this - - Returns: - str: The string that represents this - """ - return f"({self.right_ascension:.2f}, {self.declination:.2f}, {self.roll:.2f})" - - -class Vector: - """Represents a R^n Vector""" - - def __init__(self, *values: Iterable[float], numpy: np.ndarray = None): - """Creates a vector - - Args: - values (Iterable[float]): The values to give this vector - numpy (np.ndarray, optional): A numpy array to use for this Vector. Defaults to None. - - Raises: - ValueError: If the values or numpy array given can't be an array - """ - if numpy is None: - self.dimension = len(values) - self.vector = np.array(values) - else: - if len(numpy.shape) != 1: - raise ValueError( - f"Numpy array is not of 1 dimension but is of shape {numpy.shape}" - ) - self.dimension = len(numpy) - self.vector = numpy - - def cross(self, vector): - """Returns the cross product of this with some other vector - - Args: - vector (Vector): The vector to cross this with - - Returns: - Vector: The cross of this and vector - """ - return Vector(self, numpy=np.cross(self.vector, vector.vector)) - - @property - def norm(self) -> float: - """Is the norm of this - - Returns: - float: The norm of this - """ - return np.linalg.norm(self.vector) - - def normalize(self): - """Normalizes this - - Returns: - Vector: this, but with a length of one - """ - return Vector(self, numpy=self.vector / self.norm) - - def __neg__(self): - """Negates this vector - - Returns: - Vector: The negation of this - """ - return Vector(numpy=-self.vector) - - def __add__(self, other): - """Returns the sum of this with some other vector - - Args: - vector (Vector): The vector to sum this with - - Returns: - Vector: The sum of this and vector - """ - return Vector(numpy=self.vector + other.vector) - - def __sub__(self, other): - """Returns the difference between this and some other vector - - Args: - vector (Vector): The vector to subtract this from - - Returns: - Vector: The difference between this and vector - """ - return Vector(numpy=self.vector - other.vector) - - def __mul__(self, factor: float): - """Returns the product of this with some other vector - - Args: - vector (Vector): The vector to multiple this with - - Returns: - Vector: The product of this and vector - """ - return Vector(numpy=factor * self.vector) - - def __getitem__(self, index: int): - """Obtains an element of this - - Args: - index (int): The index of the element to get - - Returns: - float: The value of the element at index - """ - return self.vector[index] - - def __iter__(self) -> Iterator[float]: - """Returns the iteration object for this - - Returns: - Iterator[float]: The iteration object for this - """ - return self.vector.__iter__() - - def __eq__(self, other) -> bool: - """Evaluates if this vector is the same as the other vector - - Args: - other (Any): The vector to cross this with - - Returns: - bool: If this has the same contents as other - """ - if not isinstance(other, Vector) or self.dimension != other.dimension: - return False - for x, y in zip(self.vector, other.vector): - if not float_equals(x, y): - return False - return True - - def __hash__(self) -> int: - """Returns the hash of this - - Returns: - int: The hash of this - """ - return int(self.vector.sum().item()) - - def __str__(self) -> str: - """Returns the string representation of this - - Returns: - str: The string of this - """ - result = "" - for el in self.vector: - result += f"{el:.2f}, " - return f"({result[:-2]})" - - -class CoordinateSystem: - """Represents a 3D coordinate System""" - - def __init__(self, position: Vector = Vector(0, 0, 0), attitude: Attitude = None): - """Initializes this coordinate system with a position and rotation - - Args: - position (Vector, optional): The position of the origin. Defaults to Vector(0, 0, 0). - attitude (Attitude, optional): The rotation applied to this coordinate system. Defaults to None. - """ - if attitude is not None: - ra = attitude.right_ascension - de = attitude.declination # Negate because this is CW - roll = -attitude.roll # Negate also because this is CW - - # Step 1: Form unit x vector with right ascension and declination - # Formula: x_hat = - x_hat = Vector(np.cos(de) * np.cos(ra), np.cos(de) * np.sin(ra), np.sin(de)) - - # Step 2: Form the nominal y vector with right ascension only - y_hat_nominal = Vector(-np.sin(ra), np.cos(ra), 0) - - # Step 3: Form the nominal z vector by crossing the x and nominal y vectors - z_hat_nominal = x_hat.cross(y_hat_nominal) - - # Step 4: Rotate z_hat vector according to roll in the plane of z_hat and y_hat. - z_hat = z_hat_nominal * np.cos(roll) - y_hat_nominal * np.sin(roll) - - # Step 5: Rotate y_hat in a similar way - y_hat = y_hat_nominal * np.cos(roll) + z_hat_nominal * np.sin(roll) - - # Step 6: Store it - self.basis = np.array([x_hat, y_hat, z_hat]) - else: - self.basis = np.array([Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)]) - - # Step 7: Store the position - self.position = position - - def to_coordinate_system(self, points: Iterable[Vector]) -> Iterable[Vector]: - """Transforms vectors in the reference to the coordinate system's frame - - Args: - points (Iterable[Vector]): The points to transform - - Returns: - Iterable[Vector]: The transformed points - """ - points = np.array([pt.vector - self.position.vector for pt in points]) - if len(points.shape) == 2 and not points.shape[0] == 3 and points.shape[1] == 3: - points = points.T - basis = np.column_stack([axis.vector for axis in self.basis]) - transformed = np.linalg.solve(basis, points).T - result = [Vector(numpy=point) for point in transformed] - if len(result) == 1: - return result[0] - return result - - -CELESTIAL_SYSTEM = CoordinateSystem() diff --git a/tools/generator/test/conftest.py b/tools/generator/test/conftest.py deleted file mode 100644 index 0698f2d1..00000000 --- a/tools/generator/test/conftest.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys -from pathlib import Path - -# Add the 'src' directory to sys.path -sys.path.insert(0, str(Path(__file__).resolve().parents[1])) diff --git a/tools/generator/test/curve/test_spherical.py b/tools/generator/test/curve/test_spherical.py deleted file mode 100644 index 6ca3c83e..00000000 --- a/tools/generator/test/curve/test_spherical.py +++ /dev/null @@ -1,100 +0,0 @@ -import unittest -import numpy as np - -from src.common.constants import EARTH_RADIUS -from src.spatial.coordinate import Attitude, Vector, CoordinateSystem -from src.curve.spherical import SphericalCurveProvider - - -class SphericalGeneratorTest(unittest.TestCase): - def helper(self, attitude: Attitude, alpha_conj: float): - TEST_POINTS = 10 - - # Step 1: Generate Position Vector - position = Vector(EARTH_RADIUS / np.cos(alpha_conj), 0, 0) - - # Step 2: Generate Circle Points - center = Vector(EARTH_RADIUS * np.cos(alpha_conj), 0, 0) - radius1 = Vector(0, EARTH_RADIUS * np.sin(alpha_conj), 0) - radius2 = Vector(0, 0, EARTH_RADIUS * np.sin(alpha_conj)) - - points = np.array( - [ - (center + radius1 * np.cos(angle) + radius2 * np.sin(angle)).vector - for angle in np.linspace(0, np.pi * 2, 720) - ] - )[np.random.randint(0, 720, TEST_POINTS)] - - # Rotate using a coodrinate system - system = CoordinateSystem(attitude=attitude) - rotation_matrix = np.array([vector.vector for vector in system.basis]) - expected_points = { - Vector(numpy=vector) for vector in (rotation_matrix @ points.T).T - } - final_position = Vector(numpy=(rotation_matrix @ position.vector.T).T) - expected_center = Vector(numpy=(rotation_matrix @ center.vector.T).T) - radius1 = Vector(numpy=(rotation_matrix @ radius1.vector.T).T) - radius2 = Vector(numpy=(rotation_matrix @ radius2.vector.T).T) - - # Create the SphericalCurveProvider and generate points - curve_provider = SphericalCurveProvider(final_position) - actual_center, actual_points = curve_provider.generate_points(720) - - # First, figure out if the basic vectors are correct - self.assertEqual(expected_center, actual_center) - normal_vector = radius1.cross(radius2) - np.testing.assert_allclose( - 0, - np.dot(normal_vector.vector, curve_provider.radius.vector) - / (normal_vector.norm * curve_provider.radius.norm), - atol=1e-6, - ) - np.testing.assert_allclose( - 0, - np.dot(normal_vector.vector, curve_provider.radius2.vector) - / (normal_vector.norm * curve_provider.radius2.norm), - atol=1e-6, - ) - np.testing.assert_allclose( - 0, - np.dot(curve_provider.radius.vector, curve_provider.radius2.vector), - atol=1e-3, - ) - np.testing.assert_allclose( - EARTH_RADIUS * np.sin(alpha_conj), curve_provider.radius.norm, atol=1e-3 - ) - np.testing.assert_allclose( - EARTH_RADIUS * np.sin(alpha_conj), curve_provider.radius2.norm, atol=1e-3 - ) - - # Then, look at all points and see if the expected points are "in the array" - tally = set() - norms = [] - tolerance_angle = np.pi * 2 / 720 / 2 - tolerance_norm = ( - radius1.norm - * np.sin(tolerance_angle) - / np.sin((np.pi - tolerance_angle) / 2) - ) - for actual_pt in actual_points: - for expected_pt in expected_points: - if (actual_pt - expected_pt).norm < tolerance_norm: - tally.add(expected_pt) - norms.append(((actual_pt - expected_pt).norm)) - - self.assertEqual(len(expected_points), len(tally)) - - def test_no_rotation_small_angle(self): - self.helper(Attitude(0, 0, 0), np.pi / 6) - - def test_no_rotation_big_angle(self): - self.helper(Attitude(0, 0, 0), np.pi * 2 / 5) - - def test_simple_rotatation(self): - self.helper(Attitude(np.pi / 4, 0, 0), np.pi / 9) - - def test_compound_rotatation(self): - self.helper(Attitude(np.pi / 6, -np.pi / 3, 0), np.pi / 5) - - def test_full_rotations(self): - self.helper(Attitude(np.pi / 2, -np.pi / 7, -np.pi / 11), np.pi / 100) diff --git a/tools/generator/test/integration/test_integration.py b/tools/generator/test/integration/test_integration.py deleted file mode 100644 index 8b89aceb..00000000 --- a/tools/generator/test/integration/test_integration.py +++ /dev/null @@ -1,458 +0,0 @@ -import unittest -from unittest.mock import patch -import numpy as np - -from src.spatial.coordinate import Vector, Attitude, CoordinateSystem -from src.curve.spherical import SphericalCurveProvider -from src.common.constants import EARTH_RADIUS, DEFAULT_RESOLUTION, DEFAULT_PIXEL_SIZE -from src.__main__ import generate_points - - -class IntegrationTest(unittest.TestCase): - # Taking an inductive approach to this. First we manually calculate the circle we expect to see looking at the image head on, then we take those generated pixels that we know are correct, and then rotate them to get the other rotated images. Because we know our base pixels are correct and our rotation algorithm, then we can use them without worrying too much. - - def setUp(self): - import src.common.constants as constants - - constants.NUM_EARTH_POINTS = 360 - self.NUM_EARTH_POINTS = constants.NUM_EARTH_POINTS - - def test_approx_right_rotation(self): - position = Vector(EARTH_RADIUS + 1000000, 0, 0) - orientation = Attitude(180 - 25, 0, 0, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - ) - - left = 0 - for pt in actual_points: - if pt[0] >= 0: - left += 1 - self.assertGreater(left, len(actual_points) - left) - - def test_approx_left_rotation(self): - position = Vector(EARTH_RADIUS + 1000000, 0, 0) - orientation = Attitude(180 + 25, 0, 0, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - ) - - left = 0 - for pt in actual_points: - if pt[0] >= 0: - left += 1 - self.assertLess(left, len(actual_points) - left) - - def test_approx_up_rotation(self): - position = Vector(-EARTH_RADIUS - 1000000, 0, 0) - orientation = Attitude(0, -25, 0, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - ) - - up = 0 - for pt in actual_points: - if pt[1] >= 0: - up += 1 - self.assertGreater(up, len(actual_points) - up) - - def test_approx_down_rotation(self): - position = Vector(EARTH_RADIUS + 1000000, 0, 0) - orientation = Attitude(180, 25, 0, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - ) - - up = 0 - for pt in actual_points: - if pt[1] >= 0: - up += 1 - self.assertLess(up, len(actual_points) - up) - - def test_approx_inplace_rotation(self): - position = Vector(-EARTH_RADIUS - 1000000, 0, 0) - orientation = Attitude(0, 0, 59, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - ) - - up = 0 - left = 0 - for pt in actual_points: - if pt[0] >= 0: - left += 1 - if pt[1] >= 0: - up += 1 - self.assertAlmostEqual(up, len(actual_points) - up) - self.assertAlmostEqual(left, len(actual_points) - left) - - @patch("src.__main__.exit", side_effect=RuntimeError("Expected")) - def test_cannot_see_earth(self, _): - position = Vector(EARTH_RADIUS + 10000, 0, 0) - leo_threshold = 2000000 - focal_length = 0.085 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - for orientation in [ - Attitude(0, 0, 0, radians=False), - Attitude(45, 10, 0, radians=False), - Attitude(45, 20, 10, radians=False), - ]: - with self.assertRaises(RuntimeError): - generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - num_points=self.NUM_EARTH_POINTS, - ) - - def test_exact_head_on(self): - position = Vector(EARTH_RADIUS + 1000000, 0, 0) - orientation = Attitude(180, 0, 0, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - curve_provider = SphericalCurveProvider(position) - radius_len = ( - curve_provider.radius.norm - * focal_length - / (position.norm - curve_provider.center.norm) - / pixel_size - ) - r1, r2 = Vector(radius_len, 0), Vector(0, radius_len) - - expected_points = [ - r1 * np.sin(t) + r2 * np.cos(t) for t in np.linspace(0, 2 * np.pi, 20) - ] - theta_tolerance = ( - np.pi / self.NUM_EARTH_POINTS - ) # Not 2pi because we half the angle - dist_tolerance = r1.norm * theta_tolerance - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - ) - - np.testing.assert_allclose(radius_len, actual_points[0].norm) - - count = 0 - for exp_pt in expected_points: - for act_pt in actual_points: - if (exp_pt - act_pt).norm < dist_tolerance: - count += 1 - break - - self.assertEqual(len(expected_points), count) - - def test_rotation_simple(self): - position = Vector(EARTH_RADIUS + 1000000, 0, 0) - orientation = Attitude(145, 0, 0, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - curve_provider = SphericalCurveProvider(position) - center = curve_provider.center.normalize() * ( - position.norm - curve_provider.center.norm - ) - r1, r2 = curve_provider.radius, curve_provider.radius2 - rotation = CoordinateSystem( - attitude=Attitude(-(180 - 145), 0, 0, radians=False) - ) - - expected_points = rotation.to_coordinate_system( - [ - center + r1 * np.sin(t) + r2 * np.cos(t) - for t in np.linspace(0, 2 * np.pi, self.NUM_EARTH_POINTS) - ] - ) - - expected_points = [ - (Vector(vec[1], vec[2]) * (focal_length / vec[0] / pixel_size)) - for vec in expected_points - if vec[0] > focal_length - ] - - dist_tolerance = 0 - for i in range(len(expected_points) - 1): - dist_tolerance = max( - dist_tolerance, (expected_points[i] - expected_points[i + 1]).norm / 2 - ) - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - num_points=self.NUM_EARTH_POINTS, - ) - - # All points should be visible - self.assertAlmostEqual(len(expected_points), len(actual_points), delta=1) - - count = 0 - for exp_pt in expected_points: - for act_pt in actual_points: - if (exp_pt - act_pt).norm < dist_tolerance: - count += 1 - break - - self.assertAlmostEqual(len(expected_points), count, delta=1) - - def test_rotation_two(self): - position = Vector(EARTH_RADIUS + 1000000, 0, 0) - orientation = Attitude(145, 20, 0, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - curve_provider = SphericalCurveProvider(position) - center = curve_provider.center.normalize() * ( - position.norm - curve_provider.center.norm - ) - r1, r2 = curve_provider.radius, curve_provider.radius2 - rotation = CoordinateSystem( - attitude=Attitude(-(180 - 145), -20, 0, radians=False) - ) - - expected_points = rotation.to_coordinate_system( - [ - center + r1 * np.sin(t) + r2 * np.cos(t) - for t in np.linspace(0, 2 * np.pi, self.NUM_EARTH_POINTS) - ] - ) - - expected_points = [ - (Vector(vec[1], vec[2]) * (focal_length / vec[0] / pixel_size)) - for vec in expected_points - if vec[0] > focal_length - ] - - dist_tolerance = 0 - for i in range(len(expected_points) - 1): - dist_tolerance = max( - dist_tolerance, (expected_points[i] - expected_points[i + 1]).norm / 2 - ) - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - num_points=self.NUM_EARTH_POINTS, - ) - - # All points should be visible - self.assertAlmostEqual(len(expected_points), len(actual_points), delta=1) - - count = 0 - for exp_pt in expected_points: - for act_pt in actual_points: - if (exp_pt - act_pt).norm < dist_tolerance: - count += 1 - break - - self.assertAlmostEqual(len(expected_points), count, delta=1) - - def test_rotation_three(self): - position = Vector(EARTH_RADIUS + 1000000, 0, 0) - orientation = Attitude(145, 20, -60, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - curve_provider = SphericalCurveProvider(position) - center = curve_provider.center.normalize() * ( - position.norm - curve_provider.center.norm - ) - r1, r2 = curve_provider.radius, curve_provider.radius2 - rotation = CoordinateSystem( - attitude=Attitude(-(180 - 145), -20, 60, radians=False) - ) - - expected_points = rotation.to_coordinate_system( - [ - center + r1 * np.sin(t) + r2 * np.cos(t) - for t in np.linspace(0, 2 * np.pi, self.NUM_EARTH_POINTS) - ] - ) - - expected_points = [ - (Vector(vec[1], vec[2]) * (focal_length / vec[0] / pixel_size)) - for vec in expected_points - if vec[0] > focal_length - ] - - dist_tolerance = 0 - for i in range(len(expected_points) - 1): - dist_tolerance = max( - dist_tolerance, (expected_points[i] - expected_points[i + 1]).norm / 2 - ) - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - num_points=self.NUM_EARTH_POINTS, - ) - - # All points should be visible - self.assertAlmostEqual(len(expected_points), len(actual_points), delta=1) - - count = 0 - for exp_pt in expected_points: - for act_pt in actual_points: - if (exp_pt - act_pt).norm < dist_tolerance: - count += 1 - break - - self.assertAlmostEqual(len(expected_points), count, delta=1) - - def test_general(self): - nominal_system = CoordinateSystem( - attitude=Attitude(180 - 45, -45, -90, radians=False) - ) - position = -nominal_system.basis[0] * (EARTH_RADIUS + 1000000) - orientation = Attitude(180 - 45 + 45, -45 - 20, -90 + 81, radians=False) - leo_threshold = EARTH_RADIUS + 1 - focal_length = 0.012 - pixel_size = DEFAULT_PIXEL_SIZE # For convenience - x_resolution = DEFAULT_RESOLUTION - y_resolution = DEFAULT_RESOLUTION - - curve_provider = SphericalCurveProvider(position) - coordinate_system = CoordinateSystem(attitude=orientation) - expected_points = curve_provider.generate_points(self.NUM_EARTH_POINTS)[1] - - # Let it do the transformation, I'm stumped as to why me manually doing it - # doesn't work (with relative transformations) - expected_points = coordinate_system.to_coordinate_system( - [pt - position for pt in expected_points] - ) - - expected_points = [ - (Vector(vec[1], vec[2]) * (focal_length / vec[0] / pixel_size)) - for vec in expected_points - if vec[0] > focal_length - ] - - dist_tolerance = 0 - for i in range(len(expected_points) - 1): - dist_tolerance = max( - dist_tolerance, (expected_points[i] - expected_points[i + 1]).norm / 2 - ) - # self.fail(dist_tolerance) - - camera, actual_points = generate_points( - position, - orientation, - leo_threshold, - focal_length, - pixel_size, - x_resolution, - y_resolution, - num_points=self.NUM_EARTH_POINTS, - ) - - # All points should be visible - self.assertAlmostEqual(len(expected_points), len(actual_points), delta=1) - - count = 0 - for exp_pt in expected_points: - for act_pt in actual_points: - if (exp_pt - act_pt).norm < dist_tolerance: - count += 1 - break - - self.assertAlmostEqual(len(expected_points), count, delta=1) diff --git a/tools/generator/test/spatial/test_camera.py b/tools/generator/test/spatial/test_camera.py deleted file mode 100644 index 61a88304..00000000 --- a/tools/generator/test/spatial/test_camera.py +++ /dev/null @@ -1,254 +0,0 @@ -import unittest -import numpy as np - -from src.spatial.camera import Camera -from src.spatial.coordinate import Attitude, Vector - - -class CameraTest(unittest.TestCase): - """Camera Vector Transformation""" - - def test_45_transform(self): - attitude = Attitude(45, 0, 0, radians=False) - position = Vector(0, 0, 0) - RESOLUTION = 1024 - camera = Camera(position, attitude, 1, 1, RESOLUTION, RESOLUTION) - print(camera.to_camera_space([Vector(np.sqrt(2) / 2, np.sqrt(2) / 2, 0)])) - print(*camera.basis) - self.assertEqual( - Vector(1, 0, 0), - camera.to_camera_space([Vector(2**0.5 / 2, 2**0.5 / 2, 0)]), - ) - - def test_random_transformations(self): - TRANSFORMATION_TESTS = 100 - NUM_POINTS = 50 - POS_LIM = 500 - RESOLUTION = 1024 - - attitudes = [ - Attitude(*(np.random.rand(3) * np.pi * 4 - np.pi * 2)) - for _ in range(TRANSFORMATION_TESTS) - ] - positions = [ - Vector(*(np.random.rand(3) * POS_LIM * 2 - POS_LIM)) - for _ in range(TRANSFORMATION_TESTS) - ] - points = [ - [Vector(numpy=arr) for arr in group] - for group in np.random.rand(TRANSFORMATION_TESTS, NUM_POINTS, 3) - ] - - for (att, pos), pts in zip(zip(attitudes, positions), points): - system = Camera( - pos, - att, - np.random.rand(), - 1, - RESOLUTION, - RESOLUTION, - ) - - expected_points = [ - Vector(numpy=expected_point) - for expected_point in ( - np.linalg.solve( - np.column_stack([axis.vector for axis in system.basis]), - np.array([arr.vector - pos.vector for arr in pts]).T, - ).T - ) - ] - - actual_points = system.to_coordinate_system(pts) - - self.assertEqual(len(expected_points), len(actual_points)) - self.assertListEqual(expected_points, actual_points) - - """ in_camera """ - - def test_in_camera_true(self): - TESTS = 100 - NUM_POINTS = 50 - POS_LIM = 500 - RESOLUTION_LIM_LO = 3 - RESOLUTION_LIM_HI = 10 - FOCAL_LEN_LIM_LO = 1e-6 - FOCAL_LEN_LIM_HI = 1e-2 - FOCAL_LEN_RANGE = FOCAL_LEN_LIM_HI - FOCAL_LEN_LIM_LO - - attitudes = [ - Attitude(*(np.random.rand(3) * np.pi * 4 - np.pi * 2)) for _ in range(TESTS) - ] - positions = [ - Vector(*(np.random.rand(3) * POS_LIM * 2 - POS_LIM)) for _ in range(TESTS) - ] - focal_lengths = [ - np.random.rand() * FOCAL_LEN_RANGE + FOCAL_LEN_LIM_LO for _ in range(TESTS) - ] - pixel_sizes = [np.random.rand() for _ in range(TESTS)] - x_resolutions = [ - np.exp2(np.random.randint(RESOLUTION_LIM_LO, RESOLUTION_LIM_HI)) - for _ in range(TESTS) - ] - y_resolutions = [ - np.exp2(np.random.randint(RESOLUTION_LIM_LO, RESOLUTION_LIM_HI)) - for _ in range(TESTS) - ] - - parameters = [ - attitudes, - positions, - focal_lengths, - pixel_sizes, - x_resolutions, - y_resolutions, - ] - - points = [ - [ - Vector(point[0] * x_res - 1e-6, point[1] * y_res - 1e-6) - - Vector(x_res / 2, y_res / 2) - for point in group - ] - for group, x_res, y_res in zip( - np.random.rand(TESTS, NUM_POINTS, 2), x_resolutions, y_resolutions - ) - ] - - for att, pos, f, pix_size, x_res, y_res, pts in zip(*parameters, points): - cam = Camera(pos, att, f, pix_size, x_res, y_res) - - for pt in pts: - self.assertTrue(cam.in_camera(pt)) - - def test_in_camera_false(self): - TESTS = 100 - NUM_POINTS = 50 - POS_LIM = 500 - RESOLUTION_LIM_LO = 3 - RESOLUTION_LIM_HI = 10 - FOCAL_LEN_LIM_LO = 1e-6 - FOCAL_LEN_LIM_HI = 1e-2 - FOCAL_LEN_RANGE = FOCAL_LEN_LIM_HI - FOCAL_LEN_LIM_LO - - attitudes = [ - Attitude(*(np.random.rand(3) * np.pi * 4 - np.pi * 2)) for _ in range(TESTS) - ] - positions = [ - Vector(*(np.random.rand(3) * POS_LIM * 2 - POS_LIM)) for _ in range(TESTS) - ] - focal_lengths = [ - np.random.rand() * FOCAL_LEN_RANGE + FOCAL_LEN_LIM_LO for _ in range(TESTS) - ] - pixel_sizes = [np.random.rand() for _ in range(TESTS)] - x_resolutions = [ - np.exp2(np.random.randint(RESOLUTION_LIM_LO, RESOLUTION_LIM_HI)) - for _ in range(TESTS) - ] - y_resolutions = [ - np.exp2(np.random.randint(RESOLUTION_LIM_LO, RESOLUTION_LIM_HI)) - for _ in range(TESTS) - ] - - parameters = [ - attitudes, - positions, - focal_lengths, - pixel_sizes, - x_resolutions, - y_resolutions, - ] - - points = [ - [ - Vector(point[0] * x_res, point[1] * y_res) - + Vector(np.sign(point[0]) * x_res / 2, np.sign(point[1]) * y_res / 2) - for point in group - ] - for group, x_res, y_res in zip( - np.random.rand(TESTS, NUM_POINTS, 2), x_resolutions, y_resolutions - ) - ] - - for att, pos, f, pix_size, x_res, y_res, pts in zip(*parameters, points): - cam = Camera(pos, att, f, pix_size, x_res, y_res) - - for pt in pts: - self.assertFalse(cam.in_camera(pt)) - - """ Spatial to Camera (3D to 2D Projection) """ - - def test_spatial_to_camera_single(self): - position = Vector(10000, 0, 20000) - attitude = Attitude(1.0, 2.0, -3.0) - focal_length = 0.03 - x_resolution, y_resolution = 1024, 1024 - - cam = Camera(position, attitude, focal_length, 0.5, x_resolution, y_resolution) - - testVector = Vector(1, 2, 3) - expectedVector = Vector( - 2 * 2 * focal_length, 3 * 2 * focal_length - ) # f/x, but x is 1, then pixel size scales the coordinates by 2 - - actualVector = cam.spatial_to_camera(testVector) - - self.assertEqual(expectedVector, actualVector) - - def test_spatial_to_camera(self): - TESTS = 100 - NUM_POINTS = 50 - POS_LIM = 500 - RESOLUTION_LIM_LO = 3 - RESOLUTION_LIM_HI = 10 - FOCAL_LEN_LIM_LO = 1e-6 - FOCAL_LEN_LIM_HI = 1e-2 - FOCAL_LEN_RANGE = FOCAL_LEN_LIM_HI - FOCAL_LEN_LIM_LO - - attitudes = [ - Attitude(*(np.random.rand(3) * np.pi * 4 - np.pi * 2)) for _ in range(TESTS) - ] - positions = [ - Vector(*(np.random.rand(3) * POS_LIM * 2 - POS_LIM)) for _ in range(TESTS) - ] - focal_lengths = [ - np.random.rand() * FOCAL_LEN_RANGE + FOCAL_LEN_LIM_LO for _ in range(TESTS) - ] - x_resolutions = [ - np.exp2(np.random.randint(RESOLUTION_LIM_LO, RESOLUTION_LIM_HI)) - for _ in range(TESTS) - ] - y_resolutions = [ - np.exp2(np.random.randint(RESOLUTION_LIM_LO, RESOLUTION_LIM_HI)) - for _ in range(TESTS) - ] - - parameters = [attitudes, positions, focal_lengths, x_resolutions, y_resolutions] - - expected_points = [ - [ - Vector(point[0] * 2 * x_res - 1e-6, point[1] * 2 * y_res - 1e-6) - - Vector(x_res / 2, y_res / 2) - for point in group - ] - for group, x_res, y_res in zip( - np.random.rand(TESTS, NUM_POINTS, 2), x_resolutions, y_resolutions - ) - ] - - projected_points = [ - [Vector(x, x / f * point[0], x / f * point[1]) for point in group] - for group, x, f in zip( - expected_points, np.random.rand(len(expected_points)), focal_lengths - ) - ] - - for exp_pts, proj_pts, att, pos, f, x_res, y_res in zip( - expected_points, projected_points, *parameters - ): - cam = Camera(pos, att, f, 1, x_res, y_res) - - actual_pts = cam.spatial_to_camera(*proj_pts) - if None in actual_pts: - continue - self.assertListEqual(exp_pts, actual_pts) diff --git a/tools/generator/test/spatial/test_coordinate.py b/tools/generator/test/spatial/test_coordinate.py deleted file mode 100644 index d7c68520..00000000 --- a/tools/generator/test/spatial/test_coordinate.py +++ /dev/null @@ -1,109 +0,0 @@ -from src.spatial.coordinate import Vector, CoordinateSystem, Attitude, CELESTIAL_SYSTEM -import unittest -import numpy as np - - -class CoordinateTests(unittest.TestCase): - """Constructor Tests""" - - def test_normal_basis(self): - x_hat, y_hat, z_hat = CELESTIAL_SYSTEM.basis - - self.assertEqual(Vector(1, 0, 0), x_hat) - self.assertEqual(Vector(0, 1, 0), y_hat) - self.assertEqual(Vector(0, 0, 1), z_hat) - - # Answers from https://www.andre-gaschler.com/rotationconverter/ - - def test_ra_only_rotation(self): - x_hat, y_hat, z_hat = CoordinateSystem(attitude=Attitude(np.pi / 2, 0, 0)).basis - - self.assertEqual(Vector(0, 1, 0), x_hat) - self.assertEqual(Vector(-1, 0, 0), y_hat) - self.assertEqual(Vector(0, 0, 1), z_hat) - - def test_de_only_rotation(self): - x_hat, y_hat, z_hat = CoordinateSystem(attitude=Attitude(0, 5.3, 0)).basis - - self.assertEqual(Vector(0.5543743, 0, -0.8322675), x_hat) - self.assertEqual(Vector(0, 1, 0), y_hat) - self.assertEqual(Vector(0.8322675, 0, 0.5543743), z_hat) - - def test_roll_only_rotation(self): - x_hat, y_hat, z_hat = CoordinateSystem(attitude=Attitude(0, 0, 2.9)).basis - - self.assertEqual(Vector(1, 0, 0), x_hat) - self.assertEqual(Vector(0, -0.9709582, -0.2392493), y_hat) - self.assertEqual(Vector(0, 0.2392493, -0.9709582), z_hat) - - def test_ra_de_rotation(self): - x_hat, y_hat, z_hat = CoordinateSystem( - attitude=Attitude(np.pi / 4, np.pi / 4, 0) - ).basis - - self.assertEqual(Vector(0.5, 0.5, 0.7071068), x_hat) - self.assertEqual(Vector(-0.7071068, 0.7071068, 0.0), y_hat) - self.assertEqual(Vector(-0.5, -0.5, 0.7071068), z_hat) - - def test_ra_roll_rotation(self): - x_hat, y_hat, z_hat = CoordinateSystem(attitude=Attitude(-4, 0, 5)).basis - - self.assertEqual(Vector(-0.6536436, 0.7568025, 0), x_hat) - self.assertEqual(Vector(-0.2146762, -0.1854140, 0.9589243), y_hat) - self.assertEqual(Vector(0.7257163, 0.6267948, 0.2836622), z_hat) - - def test_dec_roll_rotation(self): - x_hat, y_hat, z_hat = CoordinateSystem(attitude=Attitude(0, 3.7, -4.5)).basis - - self.assertEqual(Vector(-0.8481000, 0, -0.5298361), x_hat) - self.assertEqual(Vector(-0.5179308, -0.2107958, 0.8290433), y_hat) - self.assertEqual(Vector(-0.1116872, 0.9775301, 0.1787759), z_hat) - - def test_general_initialization(self): - x_hat, y_hat, z_hat = CoordinateSystem(attitude=Attitude(0.5, 1.5, 4.5)).basis - - self.assertEqual(Vector(0.0620777, 0.0339132, 0.9974950), x_hat) - self.assertEqual(Vector(-0.7546535, -0.6524696, 0.0691477), y_hat) - self.assertEqual(Vector(0.6531802, -0.7570556, -0.0149111), z_hat) - - """ Transformation (Into System) Tests """ - - # def test_single_transformation(self): - # attitude = Attitude(np.) - - def test_random_transformations(self): - TRANSFORMATION_TESTS = 100 - NUM_POINTS = 50 - - POS_LIM = 500 - - attitudes = [ - Attitude(*(np.random.rand(3) * np.pi * 4 - np.pi * 2)) - for _ in range(TRANSFORMATION_TESTS) - ] - positions = [ - Vector(*(np.random.rand(3) * POS_LIM * 2 - POS_LIM)) - for _ in range(TRANSFORMATION_TESTS) - ] - points = [ - [Vector(numpy=arr) for arr in group] - for group in np.random.rand(TRANSFORMATION_TESTS, NUM_POINTS, 3) - ] - - for (att, pos), pts in zip(zip(attitudes, positions), points): - system = CoordinateSystem(pos, att) - - expected_points = [ - Vector(numpy=expected_point) - for expected_point in ( - np.linalg.solve( - np.column_stack([axis.vector for axis in system.basis]), - np.array([arr.vector - pos.vector for arr in pts]).T, - ).T - ) - ] - - actual_points = system.to_coordinate_system(pts) - - self.assertEqual(len(expected_points), len(actual_points)) - self.assertListEqual(expected_points, actual_points) diff --git a/tools/setup-tools-envs.sh b/tools/setup-tools-envs.sh deleted file mode 100755 index 263339ae..00000000 --- a/tools/setup-tools-envs.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Print and execute a command with a banner -execute_cmd() { - echo ">>> $*" - "$@" -} - -# Path to where to look for environment.yml files -TOOLS_DIR="/workspace/tools" - -# Detect available environment manager -detect_env_mgr() { - if command -v conda &> /dev/null; then - echo "conda" - elif command -v miniconda &> /dev/null; then - echo "miniconda" - elif command -v mamba &> /dev/null; then - echo "mamba" - elif command -v micromamba &> /dev/null; then - echo "micromamba" - else - echo "" - fi -} - -# Ensure we have a working environment manager -ENV_MGR=$(detect_env_mgr) -if [[ -z "$ENV_MGR" ]]; then - echo >&2 "No compatible environment manager found on your system." - echo >&2 "Please install one of the following before running this script:" - echo >&2 " - conda → https://docs.conda.io/en/latest/miniconda.html" - echo >&2 " - mamba → https://mamba.readthedocs.io/en/latest/" - echo >&2 " - micromamba → https://mamba.readthedocs.io/en/latest/micromamba.html" - echo >&2 - echo >&2 "After installing, re-run this script." - exit 1 -fi - -# Setup micromamba shell if it's being used -if [[ "$ENV_MGR" == *micromamba ]]; then - eval "$("$ENV_MGR" shell hook --shell bash)" -fi - -# Set the -y flag only for mamba or micromamba -USE_YES_FLAG=false -if [[ "$ENV_MGR" == "mamba" || "$ENV_MGR" == "micromamba" ]]; then - USE_YES_FLAG=true -fi - -# Process all environment.yml files under tools -find "$TOOLS_DIR" -mindepth 2 -maxdepth 2 -name environment.yml | while read -r envfile; do - envname=$(awk '/^name:/ {print $2}' "$envfile") - [[ -z "$envname" ]] && envname=$(basename "$(dirname "$envfile")") - - echo "Processing environment: $envname" - - if "$ENV_MGR" env list | grep -qw "$envname"; then - if $USE_YES_FLAG; then - execute_cmd "$ENV_MGR" env update -n "$envname" -f "$envfile" -y - else - execute_cmd "$ENV_MGR" env update -n "$envname" -f "$envfile" - fi - else - if $USE_YES_FLAG; then - execute_cmd "$ENV_MGR" env create -n "$envname" -f "$envfile" -y - else - execute_cmd "$ENV_MGR" env create -n "$envname" -f "$envfile" - fi - fi -done