From f1714c1c081b81756db41bdc5130aa253babc6c1 Mon Sep 17 00:00:00 2001 From: skapoor68 Date: Sat, 24 Feb 2024 01:20:11 +0000 Subject: [PATCH] working failure pipeline --- README.md | 2 +- ns3-sat-sim/simulator/contrib/basic-sim | 2 +- .../ground_stations_starlink_550.basic.text | 198 +++++++++ paper/satellite_networks_state/main_helper.py | 130 +++++- .../main_starlink_550_failure.py | 103 +++++ run_failure.sh | 17 + satgenpy/satgen/__init__.py | 1 + satgenpy/satgen/dynamic_state/__init__.py | 6 + ...gorithm_free_one_only_over_isls_failure.py | 121 ++++++ .../fstate_calculation_failure.py | 157 ++++++++ .../generate_dynamic_state_failure.py | 303 ++++++++++++++ .../helper_dynamic_state_failure.py | 147 +++++++ satgenpy/satgen/post_analysis/__init__.py | 2 + .../main_print_graphical_routes_and_rtt.py | 2 +- ..._print_graphical_routes_and_rtt_failure.py | 58 +++ .../main_print_routes_and_rtt.py | 2 +- .../main_print_routes_and_rtt_failure.py | 59 +++ .../print_graphical_routes_and_rtt_failure.py | 380 ++++++++++++++++++ .../print_routes_and_rtt_failure.py | 148 +++++++ satgenpy/satgen/simulate_failures/__init__.py | 1 + .../simulate_failures/parse_failure_file.py | 29 ++ 21 files changed, 1858 insertions(+), 10 deletions(-) create mode 100644 paper/satellite_networks_state/input_data/ground_stations_starlink_550.basic.text create mode 100644 paper/satellite_networks_state/main_starlink_550_failure.py create mode 100644 run_failure.sh create mode 100644 satgenpy/satgen/dynamic_state/algorithm_free_one_only_over_isls_failure.py create mode 100644 satgenpy/satgen/dynamic_state/fstate_calculation_failure.py create mode 100644 satgenpy/satgen/dynamic_state/generate_dynamic_state_failure.py create mode 100644 satgenpy/satgen/dynamic_state/helper_dynamic_state_failure.py create mode 100644 satgenpy/satgen/post_analysis/main_print_graphical_routes_and_rtt_failure.py create mode 100644 satgenpy/satgen/post_analysis/main_print_routes_and_rtt_failure.py create mode 100644 satgenpy/satgen/post_analysis/print_graphical_routes_and_rtt_failure.py create mode 100644 satgenpy/satgen/post_analysis/print_routes_and_rtt_failure.py create mode 100644 satgenpy/satgen/simulate_failures/__init__.py create mode 100644 satgenpy/satgen/simulate_failures/parse_failure_file.py diff --git a/README.md b/README.md index c15549f6b..188fa5cc3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Steps to run on ARM Mac running Ubuntu -1. Generate forwarding state: +1. Generate dynamic state: - `cd hypatia/paper/satellite_networks_state` - Run main_starlink_500.py and see file for how to run in command line diff --git a/ns3-sat-sim/simulator/contrib/basic-sim b/ns3-sat-sim/simulator/contrib/basic-sim index 3b32597c1..53b4a3a7f 160000 --- a/ns3-sat-sim/simulator/contrib/basic-sim +++ b/ns3-sat-sim/simulator/contrib/basic-sim @@ -1 +1 @@ -Subproject commit 3b32597c183e1039be7f0bede17d36d354696776 +Subproject commit 53b4a3a7fb0075d280165ae317a49e2bd912e059 diff --git a/paper/satellite_networks_state/input_data/ground_stations_starlink_550.basic.text b/paper/satellite_networks_state/input_data/ground_stations_starlink_550.basic.text new file mode 100644 index 000000000..b0a165bcf --- /dev/null +++ b/paper/satellite_networks_state/input_data/ground_stations_starlink_550.basic.text @@ -0,0 +1,198 @@ +0,Ikire,7.3781,4.1864,0 +1,Lekki,6.4412,3.4711,0 +2,Akita,39.7200,140.1025,0 +3,Hitachinaka,36.3964,140.5331,0 +4,Otaru,43.1900,140.9947,0 +5,Yamaguchi,34.1833,131.4667,0 +6,Angeles,15.1449,120.5887,0 +7,Marathon-Ontario,48.7500,-86.3667,0 +8,Saguenay-Quebec,48.4284,-71.0689,0 +9,Sambro-Creek-Nova-Scotia,44.5500,-63.8000,0 +10,St-Johns-Newfoundland,47.5615,-52.7126,0 +11,Caleta,27.9642,-114.0403,0 +12,Santiago-de-los-Caballeros,19.4511,-70.6970,0 +13,Cabo-San-Lucas,22.8905,-109.9167,0 +14,Charcas,23.7075,-101.4608,0 +15,El-Marques,20.6170,-100.2350,0 +16,Llano-Grande,25.9000,-97.5000,0 +17,Mazahua,19.0000,-100.0000,0 +18,Merida,20.9670,-89.6235,0 +19,Monterey,36.6002,-121.8947,0 +20,Peñuelas,18.0510,-66.7210,0 +21,Tapachula,14.9061,-92.2613,0 +22,Villahermosa,17.9869,-92.9303,0 +23,Ponce,18.0111,-66.6141,0 +24,Toa-Baja,18.4464,-66.2264,0 +25,Adelanto-California,34.5828,-117.4092,0 +26,Anchorage-Arkansas,35.7935,-94.3586,0 +27,Anderson-South-Carolina,34.5034,-82.6501,0 +28,Angola-Indiana,41.6340,-85.0000,0 +29,Arbuckle-California,39.0150,-122.0586,0 +30,Arlington-Oregon,45.7200,-120.2000,0 +31,Arvin-California,35.2018,-118.8331,0 +32,Atlanta-Georgia,33.7490,-84.3880,0 +33,Baxley-Georgia,31.7714,-82.3506,0 +34,Beekmantown-New-York,44.6994,-73.4669,0 +35,Bellingham-Washington,48.7519,-122.4787,0 +36,Benkelman-Nebraska,40.0500,-101.5400,0 +37,Blountsville-Alabama,34.0811,-86.5917,0 +38,Boca-Chica-Texas,25.9080,-97.2200,0 +39,Boydton-Virginia,36.6667,-78.3833,0 +40,Brewster-Washington,48.0989,-119.7864,0 +41,Broadview-Illinois,41.8589,-87.8536,0 +42,Brunswick-Maine,43.9145,-69.9653,0 +43,Butte-Montana,45.9969,-112.5348,0 +44,Cass-County-North-Dakota,46.8400,-97.2400,0 +45,Charleston-Oregon,43.3614,-124.2179,0 +46,Charleston-South-Carolina,32.7765,-79.9311,0 +47,Cheyenne-Wyoming,41.1399,-104.8202,0 +48,Clinton-Illinois,40.1526,-88.9645,0 +49,Colburn-Idaho,48.6000,-116.4000,0 +50,Columbus-Ohio,39.9612,-82.9988,0 +51,Conrad-Montana,48.1769,-111.9400,0 +52,Des-Moines-Iowa,41.5868,-93.6250,0 +53,Dumas-Texas,35.8623,-101.9664,0 +54,Elbert-Colorado,39.2200,-104.5500,0 +55,Elkton-Maryland,39.6068,-75.8330,0 +56,Evanston-Wyoming,41.2683,-110.9633,0 +57,Fairbanks-Alaska,64.8378,-147.7164,0 +58,Fort-Lauderdale-Florida,26.1224,-80.1373,0 +59,Frederick-Maryland,39.4142,-77.4105,0 +60,Gaffney-South-Carolina,35.0718,-81.6498,0 +61,Greenville-Pennsylvania,41.4090,-80.3860,0 +62,Hamshire-Texas,29.9300,-94.3600,0 +63,Hawthorne-California,33.9164,-118.3526,0 +64,Hillman-Michigan,45.0986,-83.7636,0 +65,Hillsboro-Texas,32.0000,-97.1300,0 +66,Hitterdal-Minnesota,47.0000,-96.4000,0 +67,Inman-Kansas,38.2294,-97.7890,0 +68,Kalama-Washington,46.0100,-122.8400,0 +69,Kenansville-Florida,28.1667,-81.8261,0 +70,Ketchikan-Alaska,55.3422,-131.6461,0 +71,Kuparuk-Alaska,70.3167,-149.6000,0 +72,Lawrence-Kansas,38.9717,-95.2353,0 +73,Litchfield-Connecticut,41.7449,-73.1894,0 +74,Lockport-New-York,43.1706,-78.6906,0 +75,Loring-Maine,46.9700,-67.8500,0 +76,Lunenberg-Vermont,44.4397,-71.7236,0 +77,Mandale-North-Carolina,35.0000,-78.0000,0 +78,Manistique-Michigan,45.9556,-86.2483,0 +79,Marcell-Minnesota,47.6011,-93.3161,0 +80,Marshall-Texas,32.5449,-94.3674,0 +81,McGregor-Texas,31.4200,-97.4300,0 +82,Merrillan-Wisconsin,44.4094,-90.8456,0 +83,Molokai-Hawaii,21.1500,-157.1000,0 +84,Mt-Ayr-Indiana,39.0000,-87.0000,0 +85,Murrieta-California,33.5539,-117.2139,0 +86,Nemaha-Nebraska,40.4000,-95.8500,0 +87,New-Braunfels-Texas,29.7030,-98.1245,0 +88,Nome-Alaska,64.5011,-165.4064,0 +89,Norcross-Georgia,33.9412,-84.2135,0 +90,North-Bend-Washington,47.4957,-121.7860,0 +91,Olympia-Washington,47.0379,-122.9007,0 +92,Panaca-Nevada,37.8194,-114.3861,0 +93,Port-Matilda-Pennsylvania,40.8000,-78.0500,0 +94,Prosser-Washington,46.2067,-119.7642,0 +95,Punta-Gorda-Florida,26.9298,-82.0454,0 +96,Quincy-Washington,47.2342,-119.8526,0 +97,Redmond-Washington,47.6730,-122.1180,0 +98,Richardson-Texas,32.9483,-96.7299,0 +99,Roberts-Wisconsin,44.9861,-92.4475,0 +100,Robbins-California,38.2350,-121.7750,0 +101,Robertsdale-Alabama,30.5562,-87.7050,0 +102,Rolette-North-Dakota,48.7719,-99.8400,0 +103,Roll-Arizona,35.2167,-113.6667,0 +104,Romulus-New-York,42.9117,-76.8011,0 +105,San-Antonio-Texas,29.4241,-98.4936,0 +106,Sanderson-Texas,30.1389,-102.3997,0 +107,Savanna-Oklahoma,34.8400,-95.8397,0 +108,Savannah-Tennessee,35.2269,-88.2494,0 +109,Sheffield-Illinois,41.3589,-89.6289,0 +110,Slope-County-North-Dakota,46.4000,-103.5000,0 +111,Springer-Oklahoma,34.5000,-97.1000,0 +112,Sullivan-Maine,44.5433,-68.2097,0 +113,The-Dalles-Oregon,45.5946,-121.1787,0 +114,Tionesta-California,40.9500,-124.1000,0 +115,Tracy-City-Tennessee,35.2522,-85.7256,0 +116,Unalaska-Alaska,53.8800,-166.5300,0 +117,Vernon-Utah,40.3494,-112.4694,0 +118,Warren-Missouri,38.7628,-91.1403,0 +119,Wichita-Falls-Texas,33.9137,-98.4934,0 +120,Wise-North-Carolina,36.9742,-82.5750,0 +121,York-Pennsylvania,39.9626,-76.7277,0 +122,Villenave-d'Ornon,44.7900,-0.5700,0 +123,Aerzen,52.0000,9.0000,0 +124,Usingen,50.3500,8.6000,0 +125,Ballinspittle,51.7000,-8.6500,0 +126,Elfordstown,51.8000,-8.3000,0 +127,Foggia,41.4623,15.5440,0 +128,Marsala,37.8000,12.4500,0 +129,Milano,45.4642,9.1900,0 +130,Kaunas,54.8985,23.9036,0 +131,Tromso,69.6496,18.9560,0 +132,Wola-Krobowska,52.0000,20.0000,0 +133,Alfouvar-de-Cima,40.0000,-8.0000,0 +134,Coviha,40.0000,-8.0000,0 +135,Ibi,38.6250,-0.5700,0 +136,Lepe,37.2500,-7.2000,0 +137,Villarejo-de-Salvanes,40.2000,-3.3000,0 +138,Chalfont-Grove,51.6000,-0.6000,0 +139,Fawley,50.8000,-1.3500,0 +140,Goonhilly,50.0000,-5.2000,0 +141,Hoo,51.4000,0.5000,0 +142,Isle-of-Man,54.2361,-4.5481,0 +143,Morn-Hill,51.0833,-1.3167,0 +144,Wherstead,52.0000,1.1000,0 +145,Woodwalton,52.4000,-0.1000,0 +146,Murayjat,24.0000,45.0000,0 +147,Muallim,24.0000,45.0000,0 +148,Bogantungan,-23.0000,147.0000,0 +149,Boorowa,-34.4333,148.7000,0 +150,Broken-Hill,-31.9500,141.4333,0 +151,Bulla-Bulling,-31.0000,121.0000,0 +152,Calrossie,-20.0000,148.0000,0 +153,Canyonleigh,-34.5000,150.0000,0 +154,Cataby,-30.0000,115.0000,0 +155,Cobargo,-36.3000,149.8000,0 +156,Ki-Ki,-36.0000,140.0000,0 +157,Merredin,-31.5000,118.2667,0 +158,Pimba,-31.0000,136.0000,0 +159,Springbrook-Creek,-28.0000,153.0000,0 +160,Tea-Gardens,-32.6667,152.1667,0 +161,Toonpan,-19.3333,146.7500,0 +162,Torrumbarry,-35.8000,144.0000,0 +163,Wagin,-33.3000,117.3500,0 +164,Warra,-27.0000,150.0000,0 +165,Willows,-19.0000,146.0000,0 +166,Suva,-18.1416,178.4419,0 +167,Awarua,-46.6000,168.3333,0 +168,Cleavdon,51.0000,-2.8000,0 +169,Cromwell,45.0000,-81.0000,0 +170,Hinds,-43.9000,171.8000,0 +171,Puwera,-41.0000,174.0000,0 +172,Te-Hana,-36.0000,174.0000,0 +173,Falda-del-Carmen,-31.0000,-64.0000,0 +174,Camaçari,-12.7000,-38.3200,0 +175,Guarapari,-20.6500,-40.5000,0 +176,Itaboraí,-22.7500,-42.8600,0 +177,João-Câmara,-5.5400,-35.8000,0 +178,Luz,-19.8000,-43.9500,0 +179,Manaus,-3.1190,-60.0217,0 +180,Montes-Carlos,-20.0000,-45.0000,0 +181,Mossoró,-5.1875,-37.3442,0 +182,Passa-Quatro,-22.4000,-44.9700,0 +183,Porto-Alegre,-30.0346,-51.2177,0 +184,Presidente-Prudente,-22.1200,-51.3900,0 +185,Rio-Negro,-26.1000,-49.8000,0 +186,Santana-de-Parnaíba,-23.4447,-46.9178,0 +187,São-Gonçalo-do-Amarante,-5.7900,-35.3200,0 +188,Surubim,-7.8400,-35.7600,0 +189,Uruguaiana,-29.7500,-57.0900,0 +190,Caldera,-27.0667,-70.8333,0 +191,Noviciado,-33.5000,-70.7000,0 +192,Puerto-Montt,-41.4719,-72.9361,0 +193,Puerto-Saavedra,-38.7833,-73.4000,0 +194,Punta-Arenas,-53.1500,-70.9167,0 +195,San-Clemente,-35.6000,-71.3500,0 +196,Santa-Elena,-2.2267,-80.8583,0 +197,Willemstad,12.1000,-68.9167,0 \ No newline at end of file diff --git a/paper/satellite_networks_state/main_helper.py b/paper/satellite_networks_state/main_helper.py index 932dc921f..6f58eb14a 100644 --- a/paper/satellite_networks_state/main_helper.py +++ b/paper/satellite_networks_state/main_helper.py @@ -79,9 +79,10 @@ def calculate( # Ground stations print("Generating ground stations...") if gs_selection == "ground_stations_top_100": + # write the GS file to the gen_data directory satgen.extend_ground_stations( - "input_data/ground_stations_cities_sorted_by_estimated_2025_pop_top_100.basic.txt", - output_generated_data_dir + "/" + name + "/ground_stations.txt" + "input_data/ground_stations_cities_sorted_by_estimated_2025_pop_top_100.basic.txt", # input GS file + output_generated_data_dir + "/" + name + "/ground_stations.txt" # gen_data output GS file ) elif gs_selection == "ground_stations_paris_moscow_grid": satgen.extend_ground_stations( @@ -94,7 +95,7 @@ def calculate( # TLEs print("Generating TLEs...") satgen.generate_tles_from_scratch_manual( - output_generated_data_dir + "/" + name + "/tles.txt", + output_generated_data_dir + "/" + name + "/tles.txt", # gen_data/"constellation_name"/tles.txt self.NICE_NAME, self.NUM_ORBS, self.NUM_SATS_PER_ORB, @@ -107,8 +108,8 @@ def calculate( # ISLs print("Generating ISLs...") - if isl_selection == "isls_plus_grid": - satgen.generate_plus_grid_isls( + if isl_selection == "isls_plus_grid": # create the ISL file in the gen_data directory + satgen.generate_plus_grid_isls( # each line in the ISL file is a link between two satellite indices output_generated_data_dir + "/" + name + "/isls.txt", self.NUM_ORBS, self.NUM_SATS_PER_ORB, @@ -143,7 +144,7 @@ def calculate( raise ValueError("Unknown dynamic state algorithm: " + dynamic_state_algorithm) print("Generating GSL interfaces info..") - satgen.generate_simple_gsl_interfaces_info( + satgen.generate_simple_gsl_interfaces_info( # generates the GSL info file; each node (satellite and ground station) has a id, # of GSL interfaces, and max bandwidth output_generated_data_dir + "/" + name + "/gsl_interfaces_info.txt", self.NUM_ORBS * self.NUM_SATS_PER_ORB, len(ground_stations), @@ -166,3 +167,120 @@ def calculate( dynamic_state_algorithm, True ) + + def calculate_failure( + self, + output_generated_data_dir, # Final directory in which the result will be placed + duration_s, + time_step_ms, + isl_selection, # isls_{none, plus_grid} + gs_selection, # ground_stations_{top_100, paris_moscow_grid} + dynamic_state_algorithm, # algorithm_{free_one_only_{gs_relays,_over_isls}, paired_many_only_over_isls} + num_threads + ): + + # Add base name to setting + name = self.BASE_NAME + "_" + isl_selection + "_" + gs_selection + "_" + dynamic_state_algorithm + + # Create output directories + if not os.path.isdir(output_generated_data_dir): + os.makedirs(output_generated_data_dir, exist_ok=True) + if not os.path.isdir(output_generated_data_dir + "/" + name): + os.makedirs(output_generated_data_dir + "/" + name, exist_ok=True) + + # Ground stations + print("Generating ground stations...") + if gs_selection == "ground_stations_top_100": + # write the GS file to the gen_data directory + satgen.extend_ground_stations( + "input_data/ground_stations_cities_sorted_by_estimated_2025_pop_top_100.basic.txt", # input GS file + output_generated_data_dir + "/" + name + "/ground_stations.txt" # gen_data output GS file + ) + elif gs_selection == "ground_stations_paris_moscow_grid": + satgen.extend_ground_stations( + "input_data/ground_stations_paris_moscow_grid.basic.txt", + output_generated_data_dir + "/" + name + "/ground_stations.txt" + ) + else: + raise ValueError("Unknown ground station selection: " + gs_selection) + + # TLEs + print("Generating TLEs...") + satgen.generate_tles_from_scratch_manual( + output_generated_data_dir + "/" + name + "/tles.txt", # gen_data/"constellation_name"/tles.txt + self.NICE_NAME, + self.NUM_ORBS, + self.NUM_SATS_PER_ORB, + self.PHASE_DIFF, + self.INCLINATION_DEGREE, + self.ECCENTRICITY, + self.ARG_OF_PERIGEE_DEGREE, + self.MEAN_MOTION_REV_PER_DAY + ) + + # ISLs + print("Generating ISLs...") + if isl_selection == "isls_plus_grid": # create the ISL file in the gen_data directory + satgen.generate_plus_grid_isls( # each line in the ISL file is a link between two satellite indices + output_generated_data_dir + "/" + name + "/isls.txt", + self.NUM_ORBS, + self.NUM_SATS_PER_ORB, + isl_shift=0, + idx_offset=0 + ) + elif isl_selection == "isls_none": + satgen.generate_empty_isls( + output_generated_data_dir + "/" + name + "/isls.txt" + ) + else: + raise ValueError("Unknown ISL selection: " + isl_selection) + + # Description + print("Generating description...") + satgen.generate_description( + output_generated_data_dir + "/" + name + "/description.txt", + self.MAX_GSL_LENGTH_M, + self.MAX_ISL_LENGTH_M + ) + + # GSL interfaces + ground_stations = satgen.read_ground_stations_extended( + output_generated_data_dir + "/" + name + "/ground_stations.txt" + ) + if dynamic_state_algorithm == "algorithm_free_one_only_gs_relays" \ + or dynamic_state_algorithm == "algorithm_free_one_only_over_isls": + gsl_interfaces_per_satellite = 1 + elif dynamic_state_algorithm == "algorithm_paired_many_only_over_isls": + gsl_interfaces_per_satellite = len(ground_stations) + else: + raise ValueError("Unknown dynamic state algorithm: " + dynamic_state_algorithm) + + print("Generating GSL interfaces info..") + satgen.generate_simple_gsl_interfaces_info( # generates the GSL info file; each node (satellite and ground station) has a id, # of GSL interfaces, and max bandwidth + output_generated_data_dir + "/" + name + "/gsl_interfaces_info.txt", + self.NUM_ORBS * self.NUM_SATS_PER_ORB, + len(ground_stations), + gsl_interfaces_per_satellite, # GSL interfaces per satellite + 1, # (GSL) Interfaces per ground station + 1, # Aggregate max. bandwidth satellite (unit unspecified) + 1 # Aggregate max. bandwidth ground station (same unspecified unit) + ) + + # Failure file + print("Parsing failure file...") + failure_table = satgen.parse_failure_file("input_data/failure_config_1.txt") + + # Forwarding state + print("Generating forwarding state...") + satgen.help_dynamic_state_failure( + output_generated_data_dir, + num_threads, # Number of threads + name, + time_step_ms, + duration_s, + self.MAX_GSL_LENGTH_M, + self.MAX_ISL_LENGTH_M, + dynamic_state_algorithm, + failure_table, + True + ) \ No newline at end of file diff --git a/paper/satellite_networks_state/main_starlink_550_failure.py b/paper/satellite_networks_state/main_starlink_550_failure.py new file mode 100644 index 000000000..d356fc93e --- /dev/null +++ b/paper/satellite_networks_state/main_starlink_550_failure.py @@ -0,0 +1,103 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys +import math +from main_helper import MainHelper + +# WGS72 value; taken from https://geographiclib.sourceforge.io/html/NET/NETGeographicLib_8h_source.html +EARTH_RADIUS = 6378135.0 + +# GENERATION CONSTANTS + +BASE_NAME = "starlink_550_failure_1" +NICE_NAME = "Starlink-550_failure_1" + +# STARLINK 550 + +ECCENTRICITY = 0.0000001 # Circular orbits are zero, but pyephem does not permit 0, so lowest possible value +ARG_OF_PERIGEE_DEGREE = 0.0 +PHASE_DIFF = True + +################################################################ +# The below constants are taken from Starlink's FCC filing as below: +# [1]: https://fcc.report/IBFS/SAT-MOD-20190830-00087 +################################################################ + +MEAN_MOTION_REV_PER_DAY = 15.19 # Altitude ~550 km +ALTITUDE_M = 550000 # Altitude ~550 km + +# From https://fcc.report/IBFS/SAT-MOD-20181108-00083/1569860.pdf (minimum angle of elevation: 25 deg) +SATELLITE_CONE_RADIUS_M = 940700 + +MAX_GSL_LENGTH_M = math.sqrt(math.pow(SATELLITE_CONE_RADIUS_M, 2) + math.pow(ALTITUDE_M, 2)) + +# ISLs are not allowed to dip below 80 km altitude in order to avoid weather conditions +MAX_ISL_LENGTH_M = 2 * math.sqrt(math.pow(EARTH_RADIUS + ALTITUDE_M, 2) - math.pow(EARTH_RADIUS + 80000, 2)) + +NUM_ORBS = 72 +NUM_SATS_PER_ORB = 22 +INCLINATION_DEGREE = 53 + +################################################################ + + +main_helper = MainHelper( + BASE_NAME, + NICE_NAME, + ECCENTRICITY, + ARG_OF_PERIGEE_DEGREE, + PHASE_DIFF, + MEAN_MOTION_REV_PER_DAY, + ALTITUDE_M, + MAX_GSL_LENGTH_M, + MAX_ISL_LENGTH_M, + NUM_ORBS, + NUM_SATS_PER_ORB, + INCLINATION_DEGREE, +) + + +def main(): + args = sys.argv[1:] + if len(args) != 6: + print("Must supply exactly six arguments") + print("Usage: python main_starlink_550_failure.py [duration (s)] [time step (ms)] " + "[isls_plus_grid / isls_none] " + "[ground_stations_{top_100, paris_moscow_grid}] " + "[algorithm_{free_one_only_over_isls, free_one_only_gs_relays, paired_many_only_over_isls}] " + "[num threads]") + exit(1) + else: + main_helper.calculate_failure( + "gen_data", + int(args[0]), + int(args[1]), + args[2], + args[3], + args[4], + int(args[5]), + ) + + +if __name__ == "__main__": + main() diff --git a/run_failure.sh b/run_failure.sh new file mode 100644 index 000000000..cbaf39a48 --- /dev/null +++ b/run_failure.sh @@ -0,0 +1,17 @@ +echo "Running device failure pipeline..." + +duration=30 # in seconds +timestep=1000 # in milliseconds +isls_choice="isls_plus_grid" # "isls_none" or "isls_plus_grid" +gs_choice="paris_moscow_grid" # "top_100" or "paris_moscow_grid" +algorithm="free_one_only_over_isls" # "free_one_only_gs_relays" or "free_one_only_over_isls" +num_threads=5 +src=0 # Paris +dest=76 # Moscow +constellation_name="starlink_550_failure_1_$isls_choice_ground_stations_$gs_choice_algorithm_$algorithm" + +cd paper/satellite_networks_state +python main_starlink_550_failure.py "$duration" "$timestep" "$isls_choice" "ground_stations_$gs_choice" "algorithm_$algorithm" "$num_threads" + +cd ../../satgenpy +python -m satgen.post_analysis.main_print_graphical_routes_and_rtt_failure ../out ../paper/satellite_networks_state/gen_data/$constellation_name diff --git a/satgenpy/satgen/__init__.py b/satgenpy/satgen/__init__.py index 62d8f9dcf..59f95f04d 100644 --- a/satgenpy/satgen/__init__.py +++ b/satgenpy/satgen/__init__.py @@ -6,3 +6,4 @@ from .description import * from .post_analysis import * from .distance_tools import * +from .simulate_failures import * diff --git a/satgenpy/satgen/dynamic_state/__init__.py b/satgenpy/satgen/dynamic_state/__init__.py index 19b75ca25..9d1265eba 100644 --- a/satgenpy/satgen/dynamic_state/__init__.py +++ b/satgenpy/satgen/dynamic_state/__init__.py @@ -4,3 +4,9 @@ from .generate_dynamic_state import ( generate_dynamic_state ) +from .helper_dynamic_state_failure import ( + help_dynamic_state_failure +) +from .generate_dynamic_state_failure import ( + generate_dynamic_state_failure +) diff --git a/satgenpy/satgen/dynamic_state/algorithm_free_one_only_over_isls_failure.py b/satgenpy/satgen/dynamic_state/algorithm_free_one_only_over_isls_failure.py new file mode 100644 index 000000000..0c9c99a7c --- /dev/null +++ b/satgenpy/satgen/dynamic_state/algorithm_free_one_only_over_isls_failure.py @@ -0,0 +1,121 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from .fstate_calculation_failure import * + + +def algorithm_free_one_only_over_isls_failure( + output_dynamic_state_dir, + time_since_epoch_ns, + satellites, + ground_stations, + active_satellite_ids, + active_ground_station_ids, + sat_net_graph_only_satellites_with_isls, + ground_station_satellites_in_range, + num_isls_per_sat, + sat_neighbor_to_if, + list_gsl_interfaces_info, + prev_output, + enable_verbose_logs +): + """ + FREE-ONE ONLY OVER INTER-SATELLITE LINKS ALGORITHM + + "one" + This algorithm assumes that every satellite and ground station has exactly 1 GSL interface. + + "free" + This 1 interface is bound to a maximum outgoing bandwidth, but can send to any other + GSL interface (well, satellite -> ground-station, and ground-station -> satellite) in + range. ("free") There is no reciprocation of the bandwidth asserted. + + "only_over_isls" + It calculates a forwarding state, which is essentially a single shortest path. + It only considers paths which go over the inter-satellite network, and does not make use of ground + stations relay. This means that every path looks like: + (src gs) - (sat) - (sat) - ... - (sat) - (dst gs) + + """ + + if enable_verbose_logs: + print("\nALGORITHM: FREE ONE ONLY OVER ISLS") + + # Check the graph + if sat_net_graph_only_satellites_with_isls.number_of_nodes() != len(satellites): + raise ValueError("Number of nodes in the graph does not match the number of satellites") + for sid in range(len(satellites)): + for n in sat_net_graph_only_satellites_with_isls.neighbors(sid): + if n >= len(satellites): + raise ValueError("Graph cannot contain satellite-to-ground-station links") + + ################################# + # BANDWIDTH STATE + # + + # There is only one GSL interface for each node (pre-condition), which as-such will get the entire bandwidth + output_filename = output_dynamic_state_dir + "/gsl_if_bandwidth_" + str(time_since_epoch_ns) + ".txt" + if enable_verbose_logs: + print(" > Writing interface bandwidth state to: " + output_filename) + with open(output_filename, "w+") as f_out: + if time_since_epoch_ns == 0: + for node_id in active_satellite_ids: + f_out.write("%d,%d,%f\n" + % (node_id, num_isls_per_sat[node_id], + list_gsl_interfaces_info[node_id]["aggregate_max_bandwidth"])) + for node_id in active_ground_station_ids: + f_out.write("%d,%d,%f\n" + % (node_id, 0, list_gsl_interfaces_info[node_id]["aggregate_max_bandwidth"])) + + ################################# + # FORWARDING STATE + # + + # Previous forwarding state (to only write delta) + prev_fstate = None + if prev_output is not None: + prev_fstate = prev_output["fstate"] + + # GID to satellite GSL interface index + gid_to_sat_gsl_if_idx = [0] * len(ground_stations) # (Only one GSL interface per satellite, so the first) + + # Forwarding state using shortest paths + fstate = calculate_fstate_shortest_path_without_gs_relaying_failure( + output_dynamic_state_dir, + time_since_epoch_ns, + active_satellite_ids, + active_ground_station_ids, + sat_net_graph_only_satellites_with_isls, + num_isls_per_sat, + gid_to_sat_gsl_if_idx, + ground_station_satellites_in_range, + sat_neighbor_to_if, + prev_fstate, + enable_verbose_logs + ) + + if enable_verbose_logs: + print("") + + return { + "fstate": fstate + } diff --git a/satgenpy/satgen/dynamic_state/fstate_calculation_failure.py b/satgenpy/satgen/dynamic_state/fstate_calculation_failure.py new file mode 100644 index 000000000..9bb44a11e --- /dev/null +++ b/satgenpy/satgen/dynamic_state/fstate_calculation_failure.py @@ -0,0 +1,157 @@ +import math +import networkx as nx + + +def calculate_fstate_shortest_path_without_gs_relaying_failure( + output_dynamic_state_dir, + time_since_epoch_ns, + active_satellite_ids, + active_ground_station_ids, + sat_net_graph_only_satellites_with_isls, + num_isls_per_sat, + gid_to_sat_gsl_if_idx, + ground_station_satellites_in_range_candidates, + sat_neighbor_to_if, + prev_fstate, + enable_verbose_logs +): + + # Calculate shortest path distances + if enable_verbose_logs: + print(" > Calculating Floyd-Warshall for graph without ground-station relays") + # (Note: Numpy has a deprecation warning here because of how networkx uses matrices) + distance_map = nx.floyd_warshall_numpy(sat_net_graph_only_satellites_with_isls) + + # Forwarding state + fstate = {} + + # Now write state to file for complete graph + output_filename = output_dynamic_state_dir + "/fstate_" + str(time_since_epoch_ns) + ".txt" + if enable_verbose_logs: + print(" > Writing forwarding state to: " + output_filename) + with open(output_filename, "w+") as f_out: + + # Satellites to ground stations + # From the satellites attached to the destination ground station, + # select the one which promises the shortest path to the destination ground station (getting there + last hop) + dist_satellite_to_ground_station = {} + for curr in active_satellite_ids: + for dst_gs_node_id in active_ground_station_ids: # dst_gs_node_gid is already offset by 1584 + dst_gid = dst_gs_node_id - 1584 + # Among the satellites in range of the destination ground station, + # find the one which promises the shortest distance + possible_dst_sats = ground_station_satellites_in_range_candidates[dst_gid] + if isinstance(possible_dst_sats, int): + continue + possibilities = [] + for b in possible_dst_sats: + if not math.isinf(distance_map[(curr, b[1])]): # Must be reachable + possibilities.append( + ( + distance_map[(curr, b[1])] + b[0], + b[1] + ) + ) + possibilities = list(sorted(possibilities)) + + # By default, if there is no satellite in range for the + # destination ground station, it will be dropped (indicated by -1) + next_hop_decision = (-1, -1, -1) + distance_to_ground_station_m = float("inf") + if len(possibilities) > 0: + dst_sat = possibilities[0][1] + distance_to_ground_station_m = possibilities[0][0] + + # If the current node is not that satellite, determine how to get to the satellite + if curr != dst_sat: + + # Among its neighbors, find the one which promises the + # lowest distance to reach the destination satellite + best_distance_m = 1000000000000000 + for neighbor_id in sat_net_graph_only_satellites_with_isls.neighbors(curr): + + distance_m = ( + sat_net_graph_only_satellites_with_isls.edges[(curr, neighbor_id)]["weight"] + + + distance_map[(neighbor_id, dst_sat)] + ) + if distance_m < best_distance_m: + next_hop_decision = ( + neighbor_id, + sat_neighbor_to_if[(curr, neighbor_id)], + sat_neighbor_to_if[(neighbor_id, curr)] + ) + best_distance_m = distance_m + + else: + # This is the destination satellite, as such the next hop is the ground station itself + next_hop_decision = ( + dst_gs_node_id, + num_isls_per_sat[dst_sat] + gid_to_sat_gsl_if_idx[dst_gid], + 0 + ) + + # In any case, save the distance of the satellite to the ground station to re-use + # when we calculate ground station to ground station forwarding + dist_satellite_to_ground_station[(curr, dst_gs_node_id)] = distance_to_ground_station_m + # Write to forwarding state + # curr is the active satellite id + if not prev_fstate or prev_fstate.get((curr, dst_gs_node_id), None) != next_hop_decision: + f_out.write("%d,%d,%d,%d,%d\n" % ( + curr, + dst_gs_node_id, + next_hop_decision[0], + next_hop_decision[1], + next_hop_decision[2] + )) + fstate[(curr, dst_gs_node_id)] = next_hop_decision + + # Ground stations to ground stations + # Choose the source satellite which promises the shortest path + for src_gs_node_id in active_ground_station_ids: + for dst_gs_node_id in active_ground_station_ids: + if src_gs_node_id != dst_gs_node_id: + src_gid = src_gs_node_id - 1584 + dst_gid = dst_gs_node_id - 1584 + + # Among the satellites in range of the source ground station, + # find the one which promises the shortest distance + possible_src_sats = ground_station_satellites_in_range_candidates[src_gid] + if isinstance(possible_src_sats, int): + continue + possibilities = [] + for a in possible_src_sats: + best_distance_offered_m = dist_satellite_to_ground_station[(a[1], dst_gs_node_id)] + if not math.isinf(best_distance_offered_m): + possibilities.append( + ( + a[0] + best_distance_offered_m, + a[1] + ) + ) + possibilities = sorted(possibilities) + + # By default, if there is no satellite in range for one of the + # ground stations, it will be dropped (indicated by -1) + next_hop_decision = (-1, -1, -1) + if len(possibilities) > 0: + src_sat_id = possibilities[0][1] + next_hop_decision = ( + src_sat_id, + 0, + num_isls_per_sat[src_sat_id] + gid_to_sat_gsl_if_idx[src_gid] + ) + + # Update forwarding state + if not prev_fstate or prev_fstate.get((src_gs_node_id, dst_gs_node_id), None) != next_hop_decision: + f_out.write("%d,%d,%d,%d,%d\n" % ( + src_gs_node_id, + dst_gs_node_id, + next_hop_decision[0], + next_hop_decision[1], + next_hop_decision[2] + )) + fstate[(src_gs_node_id, dst_gs_node_id)] = next_hop_decision + + # Finally return result + return fstate diff --git a/satgenpy/satgen/dynamic_state/generate_dynamic_state_failure.py b/satgenpy/satgen/dynamic_state/generate_dynamic_state_failure.py new file mode 100644 index 000000000..f5879e2a8 --- /dev/null +++ b/satgenpy/satgen/dynamic_state/generate_dynamic_state_failure.py @@ -0,0 +1,303 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from satgen.distance_tools import * +from astropy import units as u +import math +import networkx as nx +import numpy as np +from .algorithm_free_one_only_gs_relays import algorithm_free_one_only_gs_relays +from .algorithm_free_one_only_over_isls_failure import algorithm_free_one_only_over_isls_failure +from .algorithm_paired_many_only_over_isls import algorithm_paired_many_only_over_isls +from .algorithm_free_gs_one_sat_many_only_over_isls import algorithm_free_gs_one_sat_many_only_over_isls + + +def generate_dynamic_state_failure( + output_dynamic_state_dir, + epoch, + simulation_end_time_ns, + time_step_ns, + offset_ns, + satellites, + ground_stations, + list_isls, + list_gsl_interfaces_info, + max_gsl_length_m, + max_isl_length_m, + dynamic_state_algorithm, # Options: + # "algorithm_free_one_only_gs_relays" + # "algorithm_free_one_only_over_isls" + # "algorithm_paired_many_only_over_isls" + failure_table, + enable_verbose_logs +): + if offset_ns % time_step_ns != 0: + raise ValueError("Offset must be a multiple of time_step_ns") + prev_output = None + i = 0 + total_iterations = ((simulation_end_time_ns - offset_ns) / time_step_ns) + for time_since_epoch_ns in range(offset_ns, simulation_end_time_ns, time_step_ns): + if not enable_verbose_logs: + if i % int(math.floor(total_iterations) / 10.0) == 0: + print("Progress: calculating for T=%d (time step granularity is still %d ms)" % ( + time_since_epoch_ns, time_step_ns / 1000000 + )) + i += 1 + prev_output = generate_dynamic_state_at_failure( + output_dynamic_state_dir, + epoch, + time_since_epoch_ns, + satellites, + ground_stations, + list_isls, + list_gsl_interfaces_info, + max_gsl_length_m, + max_isl_length_m, + dynamic_state_algorithm, + prev_output, + failure_table, + enable_verbose_logs + ) + + +def generate_dynamic_state_at_failure( + output_dynamic_state_dir, + epoch, + time_since_epoch_ns, + satellites, + ground_stations, + list_isls, + list_gsl_interfaces_info, + max_gsl_length_m, + max_isl_length_m, + dynamic_state_algorithm, + prev_output, + failure_table, + enable_verbose_logs +): + if enable_verbose_logs: + print("FORWARDING STATE AT T = " + (str(time_since_epoch_ns)) + + "ns (= " + str(time_since_epoch_ns / 1e9) + " seconds)") + + ################################# + + if enable_verbose_logs: + print("\nBASIC INFORMATION") + + # Time + time = epoch + time_since_epoch_ns * u.ns + if enable_verbose_logs: + print(" > Epoch.................. " + str(epoch)) + print(" > Time since epoch....... " + str(time_since_epoch_ns) + " ns") + print(" > Absolute time.......... " + str(time)) + + # Graphs + sat_net_graph_only_satellites_with_isls = nx.Graph() + sat_net_graph_all_with_only_gsls = nx.Graph() + + # Active nodes + active_satellite_ids = list(range(len(satellites))) # 0 to 1583 + active_ground_station_ids = list(range(len(satellites), len(satellites) + len(ground_stations))) # 1584 to 1584 + len(ground_stations) + for failed_node in failure_table: + if failure_table[failed_node][0] <= time_since_epoch_ns <= failure_table[failed_node][1]: + if failed_node < len(satellites): + active_satellite_ids.remove(failed_node) + else: + active_ground_station_ids.remove(failed_node) + + # Information + # Add all nodes but don't add edges among failed nodes + for i in range(len(satellites)): + sat_net_graph_only_satellites_with_isls.add_node(i) + sat_net_graph_all_with_only_gsls.add_node(i) + for i in range(len(satellites) + len(ground_stations)): + sat_net_graph_all_with_only_gsls.add_node(i) + if enable_verbose_logs: + print(" > Active satellites............. " + str(len(active_satellite_ids))) + print(" > Active ground stations........ " + str(len(active_ground_station_ids))) + print(" > Max. range GSL......... " + str(max_gsl_length_m) + "m") + print(" > Max. range ISL......... " + str(max_isl_length_m) + "m") + + ################################# + + if enable_verbose_logs: + print("\nISL INFORMATION") + + # ISL edges + total_num_isls = 0 + num_isls_per_sat = [0] * len(satellites) + sat_neighbor_to_if = {} + for (a, b) in list_isls: + if a not in set(active_satellite_ids) or b not in set(active_satellite_ids): + continue + + # ISLs are not permitted to exceed their maximum distance + # TODO: Technically, they can (could just be ignored by forwarding state calculation), + # TODO: but practically, defining a permanent ISL between two satellites which + # TODO: can go out of distance is generally unwanted + sat_distance_m = distance_m_between_satellites(satellites[a], satellites[b], str(epoch), str(time)) + if sat_distance_m > max_isl_length_m: + raise ValueError( + "The distance between two satellites (%d and %d) " + "with an ISL exceeded the maximum ISL length (%.2fm > %.2fm at t=%dns)" + % (a, b, sat_distance_m, max_isl_length_m, time_since_epoch_ns) + ) + + # Add to networkx graph + sat_net_graph_only_satellites_with_isls.add_edge( + a, b, weight=sat_distance_m + ) + + # Interface mapping of ISLs + sat_neighbor_to_if[(a, b)] = num_isls_per_sat[a] + sat_neighbor_to_if[(b, a)] = num_isls_per_sat[b] + num_isls_per_sat[a] += 1 + num_isls_per_sat[b] += 1 + total_num_isls += 1 + + if enable_verbose_logs: + print(" > Total ISLs............. " + str(len(list_isls))) + print(" > Min. ISLs/satellite.... " + str(np.min(num_isls_per_sat))) + print(" > Max. ISLs/satellite.... " + str(np.max(num_isls_per_sat))) + + ################################# + + if enable_verbose_logs: + print("\nGSL INTERFACE INFORMATION") + + satellite_gsl_if_count_list = [list_gsl_interfaces_info[i]["number_of_interfaces"] for i in active_satellite_ids] + ground_station_gsl_if_count_list = [list_gsl_interfaces_info[i]["number_of_interfaces"] for i in active_ground_station_ids] + + if enable_verbose_logs: + print(" > Min. GSL IFs/satellite........ " + str(np.min(satellite_gsl_if_count_list))) + print(" > Max. GSL IFs/satellite........ " + str(np.max(satellite_gsl_if_count_list))) + print(" > Min. GSL IFs/ground station... " + str(np.min(ground_station_gsl_if_count_list))) + print(" > Max. GSL IFs/ground_station... " + str(np.max(ground_station_gsl_if_count_list))) + + ################################# + + if enable_verbose_logs: + print("\nGSL IN-RANGE INFORMATION") + + # What satellites can a ground station see + ground_station_satellites_in_range = [0] * len(ground_stations) + for ground_station in ground_stations: + if (ground_station['gid'] + 1584) not in active_ground_station_ids: + continue + + # Find satellites in range + satellites_in_range = [] + for sid in active_satellite_ids: + distance_m = distance_m_ground_station_to_satellite( + ground_station, + satellites[sid], + str(epoch), + str(time) + ) + if distance_m <= max_gsl_length_m: + satellites_in_range.append((distance_m, sid)) + sat_net_graph_all_with_only_gsls.add_edge( + sid, len(satellites) + ground_station["gid"], weight=distance_m + ) + + ground_station_satellites_in_range[ground_station["gid"]] = satellites_in_range + + # Print how many are in range + ground_station_num_in_range = list(map(lambda x: len(x) if isinstance(x, list) else 0, ground_station_satellites_in_range)) + if enable_verbose_logs: + print(" > Min. satellites in range... " + str(np.min(ground_station_num_in_range))) + print(" > Max. satellites in range... " + str(np.max(ground_station_num_in_range))) + + ################################# + + # + # Call the dynamic state algorithm which: + # + # (a) Output the gsl_if_bandwidth_.txt files + # (b) Output the fstate_.txt files + # + if dynamic_state_algorithm == "algorithm_free_one_only_over_isls": + + return algorithm_free_one_only_over_isls_failure( + output_dynamic_state_dir, + time_since_epoch_ns, + satellites, + ground_stations, + active_satellite_ids, + active_ground_station_ids, + sat_net_graph_only_satellites_with_isls, + ground_station_satellites_in_range, + num_isls_per_sat, + sat_neighbor_to_if, + list_gsl_interfaces_info, + prev_output, + enable_verbose_logs + ) + + elif dynamic_state_algorithm == "algorithm_free_gs_one_sat_many_only_over_isls": + + return algorithm_free_gs_one_sat_many_only_over_isls( + output_dynamic_state_dir, + time_since_epoch_ns, + satellites, + ground_stations, + sat_net_graph_only_satellites_with_isls, + ground_station_satellites_in_range, + num_isls_per_sat, + sat_neighbor_to_if, + list_gsl_interfaces_info, + prev_output, + enable_verbose_logs + ) + + elif dynamic_state_algorithm == "algorithm_free_one_only_gs_relays": + + return algorithm_free_one_only_gs_relays( + output_dynamic_state_dir, + time_since_epoch_ns, + satellites, + ground_stations, + sat_net_graph_all_with_only_gsls, + num_isls_per_sat, + list_gsl_interfaces_info, + prev_output, + enable_verbose_logs + ) + + elif dynamic_state_algorithm == "algorithm_paired_many_only_over_isls": + + return algorithm_paired_many_only_over_isls( + output_dynamic_state_dir, + time_since_epoch_ns, + satellites, + ground_stations, + sat_net_graph_only_satellites_with_isls, + ground_station_satellites_in_range, + num_isls_per_sat, + sat_neighbor_to_if, + list_gsl_interfaces_info, + prev_output, + enable_verbose_logs + ) + + else: + raise ValueError("Unknown dynamic state algorithm: " + str(dynamic_state_algorithm)) diff --git a/satgenpy/satgen/dynamic_state/helper_dynamic_state_failure.py b/satgenpy/satgen/dynamic_state/helper_dynamic_state_failure.py new file mode 100644 index 000000000..64da63228 --- /dev/null +++ b/satgenpy/satgen/dynamic_state/helper_dynamic_state_failure.py @@ -0,0 +1,147 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from satgen.isls import * +from satgen.ground_stations import * +from satgen.tles import * +from satgen.interfaces import * +from .generate_dynamic_state_failure import generate_dynamic_state_failure +import os +import math +from multiprocessing.dummy import Pool as ThreadPool + + +def worker(args): + + # Extract arguments + ( + output_dynamic_state_dir, + epoch, + simulation_end_time_ns, + time_step_ns, + offset_ns, + satellites, + ground_stations, + list_isls, + list_gsl_interfaces_info, + max_gsl_length_m, + max_isl_length_m, + dynamic_state_algorithm, + failure_table, + print_logs + ) = args + + # Generate dynamic state + generate_dynamic_state_failure( + output_dynamic_state_dir, + epoch, + simulation_end_time_ns, + time_step_ns, + offset_ns, + satellites, + ground_stations, + list_isls, + list_gsl_interfaces_info, + max_gsl_length_m, + max_isl_length_m, + dynamic_state_algorithm, # Options: + # "algorithm_free_one_only_gs_relays" + # "algorithm_free_one_only_over_isls" + # "algorithm_free_gs_one_sat_many_only_over_isls" + # "algorithm_paired_many_only_over_isls" + failure_table, + print_logs + ) + + +def help_dynamic_state_failure( + output_generated_data_dir, num_threads, name, time_step_ms, duration_s, + max_gsl_length_m, max_isl_length_m, dynamic_state_algorithm, failure_table, print_logs +): + + # Directory + output_dynamic_state_dir = output_generated_data_dir + "/" + name + "/dynamic_state_" + str(time_step_ms) \ + + "ms_for_" + str(duration_s) + "s" + if not os.path.isdir(output_dynamic_state_dir): + os.makedirs(output_dynamic_state_dir) + + # In nanoseconds + simulation_end_time_ns = duration_s * 1000 * 1000 * 1000 + time_step_ns = time_step_ms * 1000 * 1000 + + num_calculations = math.floor(simulation_end_time_ns / time_step_ns) + calculations_per_thread = int(math.floor(float(num_calculations) / float(num_threads))) + num_threads_with_one_more = num_calculations % num_threads + + # Prepare arguments + current = 0 + list_args = [] + for i in range(num_threads): + + # How many time steps to calculate for + num_time_steps = calculations_per_thread + if i < num_threads_with_one_more: + num_time_steps += 1 + + # Variables (load in for each thread such that they don't interfere) + ground_stations = read_ground_stations_extended(output_generated_data_dir + "/" + name + "/ground_stations.txt") + tles = read_tles(output_generated_data_dir + "/" + name + "/tles.txt") + satellites = tles["satellites"] + list_isls = read_isls(output_generated_data_dir + "/" + name + "/isls.txt", len(satellites)) + list_gsl_interfaces_info = read_gsl_interfaces_info( + output_generated_data_dir + "/" + name + "/gsl_interfaces_info.txt", + len(satellites), + len(ground_stations) + ) + epoch = tles["epoch"] + + # Print goal + print("Thread %d does interval [%.2f ms, %.2f ms]" % ( + i, + (current * time_step_ns) / 1e6, + ((current + num_time_steps) * time_step_ns) / 1e6 + )) + + list_args.append(( + output_dynamic_state_dir, + epoch, + (current + num_time_steps) * time_step_ns + (time_step_ns if (i + 1) != num_threads else 0), + time_step_ns, + current * time_step_ns, + satellites, + ground_stations, + list_isls, + list_gsl_interfaces_info, + max_gsl_length_m, + max_isl_length_m, + dynamic_state_algorithm, + failure_table, + print_logs + )) + + current += num_time_steps + + # Run in parallel + pool = ThreadPool(num_threads) + pool.map(worker, list_args) + pool.close() + pool.join() diff --git a/satgenpy/satgen/post_analysis/__init__.py b/satgenpy/satgen/post_analysis/__init__.py index 4bd496da6..c241a7a07 100644 --- a/satgenpy/satgen/post_analysis/__init__.py +++ b/satgenpy/satgen/post_analysis/__init__.py @@ -3,6 +3,8 @@ from .analyze_rtt import analyze_rtt from .analyze_time_step_path import analyze_time_step_path from .print_graphical_routes_and_rtt import print_graphical_routes_and_rtt +from .print_routes_and_rtt_failure import print_routes_and_rtt_failure +from .print_graphical_routes_and_rtt_failure import print_graphical_routes_and_rtt_failure from .graph_tools import ( construct_graph_with_distances, compute_path_length_with_graph, diff --git a/satgenpy/satgen/post_analysis/main_print_graphical_routes_and_rtt.py b/satgenpy/satgen/post_analysis/main_print_graphical_routes_and_rtt.py index 0558e9860..3e633af74 100644 --- a/satgenpy/satgen/post_analysis/main_print_graphical_routes_and_rtt.py +++ b/satgenpy/satgen/post_analysis/main_print_graphical_routes_and_rtt.py @@ -28,7 +28,7 @@ def main(): args = sys.argv[1:] if len(args) != 6: print("Must supply exactly six arguments") - print("Usage: python -m satgen.post_analysis.main_print_graphical_routes_and_rtt.py [data_dir] " + print("Usage: python -m satgen.post_analysis.main_print_graphical_routes_and_rtt [data_dir] " "[satellite_network_dir] [dynamic_state_update_interval_ms] [end_time_s] [src] [dst]") exit(1) else: diff --git a/satgenpy/satgen/post_analysis/main_print_graphical_routes_and_rtt_failure.py b/satgenpy/satgen/post_analysis/main_print_graphical_routes_and_rtt_failure.py new file mode 100644 index 000000000..1575024dd --- /dev/null +++ b/satgenpy/satgen/post_analysis/main_print_graphical_routes_and_rtt_failure.py @@ -0,0 +1,58 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys +from satgen.post_analysis.print_graphical_routes_and_rtt_failure import print_graphical_routes_and_rtt_failure +from satgen.simulate_failures import parse_failure_file + + +def main(): + args = sys.argv[1:] + if len(args) != 6: + print("Must supply exactly six arguments") + print("Usage: python -m satgen.post_analysis.main_print_graphical_routes_and_rtt_failure [data_dir] " + "[satellite_network_dir] [dynamic_state_update_interval_ms] [end_time_s] [src] [dst]") + exit(1) + else: + core_network_folder_name = args[1].split("/")[-1] + base_output_dir = "%s/%s/%dms_for_%ds/manual" % ( + args[0], core_network_folder_name, int(args[2]), int(args[3]) + ) + print("Data dir: " + args[0]) + print("Used data dir to form base output dir: " + base_output_dir) + + print("Parsing failure file...") + failure_table = parse_failure_file("/home/skapoor68/hypatia/paper/satellite_networks_state/input_data/failure_config_1.txt") + + print_graphical_routes_and_rtt_failure( + base_output_dir, + args[1], + int(args[2]), + int(args[3]), + int(args[4]), + int(args[5]), + failure_table + ) + + +if __name__ == "__main__": + main() diff --git a/satgenpy/satgen/post_analysis/main_print_routes_and_rtt.py b/satgenpy/satgen/post_analysis/main_print_routes_and_rtt.py index 3ddccbf01..2e8877934 100644 --- a/satgenpy/satgen/post_analysis/main_print_routes_and_rtt.py +++ b/satgenpy/satgen/post_analysis/main_print_routes_and_rtt.py @@ -28,7 +28,7 @@ def main(): args = sys.argv[1:] if len(args) != 6: print("Must supply exactly six arguments") - print("Usage: python -m satgen.post_analysis.main_print_routes_and_rtt.py [data_dir] [satellite_network_dir] " + print("Usage: python -m satgen.post_analysis.main_print_routes_and_rtt [data_dir] [satellite_network_dir] " "[dynamic_state_update_interval_ms] [end_time_s] [src] [dst]") exit(1) else: diff --git a/satgenpy/satgen/post_analysis/main_print_routes_and_rtt_failure.py b/satgenpy/satgen/post_analysis/main_print_routes_and_rtt_failure.py new file mode 100644 index 000000000..8179a2a6c --- /dev/null +++ b/satgenpy/satgen/post_analysis/main_print_routes_and_rtt_failure.py @@ -0,0 +1,59 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys +from satgen.post_analysis.print_routes_and_rtt_failure import print_routes_and_rtt_failure +from satgen.simulate_failures import parse_failure_file + + +def main(): + args = sys.argv[1:] + if len(args) != 6: + print("Must supply exactly six arguments") + print("Usage: python -m satgen.post_analysis.main_print_routes_and_rtt_failure [data_dir] [satellite_network_dir] " + "[dynamic_state_update_interval_ms] [end_time_s] [src] [dst]") + exit(1) + else: + core_network_folder_name = args[1].split("/")[-1] + base_output_dir = "%s/%s/%dms_for_%ds/manual" % ( + args[0], core_network_folder_name, int(args[2]), int(args[3]) + ) + print("Data dir: " + args[0]) + print("Used data dir to form base output dir: " + base_output_dir) + + print("Parsing failure file...") + failure_table = parse_failure_file("/home/skapoor68/hypatia/paper/satellite_networks_state/input_data/failure_config_1.txt") + + print_routes_and_rtt_failure( + base_output_dir, + args[1], + int(args[2]), + int(args[3]), + int(args[4]), + int(args[5]), + "", # Must be executed in satgenpy directory + failure_table + ) + + +if __name__ == "__main__": + main() diff --git a/satgenpy/satgen/post_analysis/print_graphical_routes_and_rtt_failure.py b/satgenpy/satgen/post_analysis/print_graphical_routes_and_rtt_failure.py new file mode 100644 index 000000000..28ba63535 --- /dev/null +++ b/satgenpy/satgen/post_analysis/print_graphical_routes_and_rtt_failure.py @@ -0,0 +1,380 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from .graph_tools import * +from satgen.isls import * +from satgen.ground_stations import * +from satgen.tles import * +import exputil +import cartopy +import cartopy.crs as ccrs +import matplotlib.pyplot as plt +from matplotlib.lines import Line2D + + +GROUND_STATION_USED_COLOR = "#3b3b3b" +GROUND_STATION_UNUSED_COLOR = "black" +SATELLITE_USED_COLOR = "#a61111" +SATELLITE_UNUSED_COLOR = "red" +ISL_COLOR = "#eb6b38" + + +def print_graphical_routes_and_rtt_failure( + base_output_dir, satellite_network_dir, + dynamic_state_update_interval_ms, + simulation_end_time_s, src, dst, failure_table +): + + # Local shell + local_shell = exputil.LocalShell() + + # Dynamic state dir can be inferred + satellite_network_dynamic_state_dir = "%s/dynamic_state_%dms_for_%ds" % ( + satellite_network_dir, dynamic_state_update_interval_ms, simulation_end_time_s + ) + + # Default output dir assumes it is done manual + pdf_dir = base_output_dir + "/pdf" + data_dir = base_output_dir + "/data" + local_shell.make_full_dir(pdf_dir) + local_shell.make_full_dir(data_dir) + + # Variables (load in for each thread such that they don't interfere) + ground_stations = read_ground_stations_extended(satellite_network_dir + "/ground_stations.txt") + tles = read_tles(satellite_network_dir + "/tles.txt") + satellites = tles["satellites"] + list_isls = read_isls(satellite_network_dir + "/isls.txt", len(satellites)) + epoch = tles["epoch"] + description = exputil.PropertiesConfig(satellite_network_dir + "/description.txt") + + # Derivatives + simulation_end_time_ns = simulation_end_time_s * 1000 * 1000 * 1000 + dynamic_state_update_interval_ns = dynamic_state_update_interval_ms * 1000 * 1000 + max_gsl_length_m = exputil.parse_positive_float(description.get_property_or_fail("max_gsl_length_m")) + max_isl_length_m = exputil.parse_positive_float(description.get_property_or_fail("max_isl_length_m")) + + # For each time moment + fstate = {} + current_path = [] + rtt_ns_list = [] + for t in range(0, simulation_end_time_ns, dynamic_state_update_interval_ns): + with open(satellite_network_dynamic_state_dir + "/fstate_" + str(t) + ".txt", "r") as f_in: + for line in f_in: + spl = line.split(",") + current = int(spl[0]) + destination = int(spl[1]) + next_hop = int(spl[2]) + fstate[(current, destination)] = next_hop + + for failure in failure_table: + if failure_table[failure][0] <= t <= failure_table[failure][1]: + keys_to_update = [] + for key in fstate: + source, destination = key + next_hop = fstate[key] + if source == failure or destination == failure or next_hop == failure: + keys_to_update.append(key) + for key in keys_to_update: + fstate[key] = -1 + + # Calculate path length + path_there = get_path(src, dst, fstate) + path_back = get_path(dst, src, fstate) + if path_there is not None and path_back is not None: + length_src_to_dst_m = compute_path_length_without_graph(path_there, epoch, t, satellites, + ground_stations, list_isls, + max_gsl_length_m, max_isl_length_m) + length_dst_to_src_m = compute_path_length_without_graph(path_back, epoch, t, + satellites, ground_stations, list_isls, + max_gsl_length_m, max_isl_length_m) + rtt_ns = (length_src_to_dst_m + length_dst_to_src_m) * 1000000000.0 / 299792458.0 + else: + length_src_to_dst_m = 0.0 + length_dst_to_src_m = 0.0 + rtt_ns = 0.0 + + # Add to RTT list + rtt_ns_list.append((t, rtt_ns)) + + # Only if there is a new path, print new path + new_path = get_path(src, dst, fstate) + if current_path != new_path: + + # This is the new path + current_path = new_path + + # Write change nicely to the console + print("Change at t=" + str(t) + " ns (= " + str(t / 1e9) + " seconds)") + print(" > Path..... " + (" -- ".join(list(map(lambda x: str(x), current_path))) + if current_path is not None else "Unreachable")) + print(" > Length... " + str(length_src_to_dst_m + length_dst_to_src_m) + " m") + print(" > RTT...... %.2f ms" % (rtt_ns / 1e6)) + print("") + + # Now we make a pdf for it + pdf_filename = pdf_dir + "/graphics_%d_to_%d_time_%dms.pdf" % (src, dst, int(t / 1000000)) + f = plt.figure() + + # Projection + ax = plt.axes(projection=ccrs.PlateCarree()) + + # Background + ax.add_feature(cartopy.feature.OCEAN, zorder=0) + ax.add_feature(cartopy.feature.LAND, zorder=0, edgecolor='black', linewidth=0.2) + ax.add_feature(cartopy.feature.BORDERS, edgecolor='gray', linewidth=0.2) + + # Time moment + time_moment_str = str(epoch + t * u.ns) + + # Other satellites + for node_id in range(len(satellites)): + if node_id in failure_table: + continue + + shadow_ground_station = create_basic_ground_station_for_satellite_shadow( + satellites[node_id], + str(epoch), + time_moment_str + ) + latitude_deg = float(shadow_ground_station["latitude_degrees_str"]) + longitude_deg = float(shadow_ground_station["longitude_degrees_str"]) + + # Other satellite + plt.plot( + longitude_deg, + latitude_deg, + color=SATELLITE_UNUSED_COLOR, + fillstyle='none', + markeredgewidth=0.1, + markersize=0.5, + marker='^', + ) + plt.text( + longitude_deg + 0.5, + latitude_deg, + str(node_id), + color=SATELLITE_UNUSED_COLOR, + fontdict={"size": 1} + ) + + # # ISLs + # for isl in list_isls: + # ephem_body = satellites[isl[0]] + # ephem_body.compute(time_moment_str) + # from_latitude_deg = math.degrees(ephem_body.sublat) + # from_longitude_deg = math.degrees(ephem_body.sublong) + # + # ephem_body = satellites[isl[1]] + # ephem_body.compute(time_moment_str) + # to_latitude_deg = math.degrees(ephem_body.sublat) + # to_longitude_deg = math.degrees(ephem_body.sublong) + # + # # Plot the line + # if ground_stations[src - len(satellites)]["longitude_degrees_str"] <= \ + # from_longitude_deg \ + # <= ground_stations[dst - len(satellites)]["longitude_degrees_str"] \ + # and \ + # ground_stations[src - len(satellites)]["latitude_degrees_str"] <= \ + # from_latitude_deg \ + # <= ground_stations[dst - len(satellites)]["latitude_degrees_str"] \ + # and \ + # ground_stations[src - len(satellites)]["longitude_degrees_str"] <= \ + # to_longitude_deg \ + # <= ground_stations[dst - len(satellites)]["longitude_degrees_str"] \ + # and \ + # ground_stations[src - len(satellites)]["latitude_degrees_str"] <= \ + # to_latitude_deg \ + # <= ground_stations[dst - len(satellites)]["latitude_degrees_str"]: + # plt.plot( + # [from_longitude_deg, to_longitude_deg], + # [from_latitude_deg, to_latitude_deg], + # color='#eb6b38', linewidth=0.1, marker='', + # transform=ccrs.Geodetic(), + # ) + + # Other ground stations + for gid in range(len(ground_stations)): + if gid in failure_table: + continue + + latitude_deg = float(ground_stations[gid]["latitude_degrees_str"]) + longitude_deg = float(ground_stations[gid]["longitude_degrees_str"]) + + # Other ground station + plt.plot( + longitude_deg, + latitude_deg, + color=GROUND_STATION_UNUSED_COLOR, + fillstyle='none', + markeredgewidth=0.2, + markersize=1.0, + marker='o', + ) + + # Lines between + if current_path is not None: + for v in range(1, len(current_path)): + from_node_id = current_path[v - 1] + to_node_id = current_path[v] + + # From coordinates + if from_node_id < len(satellites): + shadow_ground_station = create_basic_ground_station_for_satellite_shadow( + satellites[from_node_id], + str(epoch), + time_moment_str + ) + from_latitude_deg = float(shadow_ground_station["latitude_degrees_str"]) + from_longitude_deg = float(shadow_ground_station["longitude_degrees_str"]) + else: + from_latitude_deg = float( + ground_stations[from_node_id - len(satellites)]["latitude_degrees_str"] + ) + from_longitude_deg = float( + ground_stations[from_node_id - len(satellites)]["longitude_degrees_str"] + ) + + # To coordinates + if to_node_id < len(satellites): + shadow_ground_station = create_basic_ground_station_for_satellite_shadow( + satellites[to_node_id], + str(epoch), + time_moment_str + ) + to_latitude_deg = float(shadow_ground_station["latitude_degrees_str"]) + to_longitude_deg = float(shadow_ground_station["longitude_degrees_str"]) + else: + to_latitude_deg = float( + ground_stations[to_node_id - len(satellites)]["latitude_degrees_str"] + ) + to_longitude_deg = float( + ground_stations[to_node_id - len(satellites)]["longitude_degrees_str"] + ) + + # Plot the line + plt.plot( + [from_longitude_deg, to_longitude_deg], + [from_latitude_deg, to_latitude_deg], + color=ISL_COLOR, linewidth=0.5, marker='', + transform=ccrs.Geodetic(), + ) + + # Across all points, we need to find the latitude / longitude to zoom into + # min_latitude = min( + # ground_stations[src - len(satellites)]["latitude_degrees_str"], + # ground_stations[dst - len(satellites)]["latitude_degrees_str"] + # ) + # max_latitude = max( + # ground_stations[src - len(satellites)]["latitude_degrees_str"], + # ground_stations[dst - len(satellites)]["latitude_degrees_str"] + # ) + # min_longitude = min( + # ground_stations[src - len(satellites)]["longitude_degrees_str"], + # ground_stations[dst - len(satellites)]["longitude_degrees_str"] + # ) + # max_longitude = max( + # ground_stations[src - len(satellites)]["longitude_degrees_str"], + # ground_stations[dst - len(satellites)]["longitude_degrees_str"] + # ) + + # Points + if current_path is not None: + for v in range(0, len(current_path)): + node_id = current_path[v] + if node_id < len(satellites): + shadow_ground_station = create_basic_ground_station_for_satellite_shadow( + satellites[node_id], + str(epoch), + time_moment_str + ) + latitude_deg = float(shadow_ground_station["latitude_degrees_str"]) + longitude_deg = float(shadow_ground_station["longitude_degrees_str"]) + # min_latitude = min(min_latitude, latitude_deg) + # max_latitude = max(max_latitude, latitude_deg) + # min_longitude = min(min_longitude, longitude_deg) + # max_longitude = max(max_longitude, longitude_deg) + # Satellite + plt.plot( + longitude_deg, + latitude_deg, + color=SATELLITE_USED_COLOR, + marker='^', + markersize=0.65, + ) + plt.text( + longitude_deg + 0.9, + latitude_deg, + str(node_id), + fontdict={"size": 2, "weight": "bold"} + ) + else: + latitude_deg = float(ground_stations[node_id - len(satellites)]["latitude_degrees_str"]) + longitude_deg = float(ground_stations[node_id - len(satellites)]["longitude_degrees_str"]) + # min_latitude = min(min_latitude, latitude_deg) + # max_latitude = max(max_latitude, latitude_deg) + # min_longitude = min(min_longitude, longitude_deg) + # max_longitude = max(max_longitude, longitude_deg) + if v == 0 or v == len(current_path) - 1: + # Endpoint (start or finish) ground station + plt.plot( + longitude_deg, + latitude_deg, + color=GROUND_STATION_USED_COLOR, + marker='o', + markersize=0.9, + ) + else: + # Intermediary ground station + plt.plot( + longitude_deg, + latitude_deg, + color=GROUND_STATION_USED_COLOR, + marker='o', + markersize=0.9, + ) + + # Zoom into region + # ax.set_extent([ + # min_longitude - 5, + # max_longitude + 5, + # min_latitude - 5, + # max_latitude + 5, + # ]) + + # Legend + ax.legend( + handles=( + Line2D([0], [0], marker='o', label="Ground station (used)", + linewidth=0, color='#3b3b3b', markersize=5), + Line2D([0], [0], marker='o', label="Ground station (unused)", + linewidth=0, color='black', markersize=5, fillstyle='none', markeredgewidth=0.5), + Line2D([0], [0], marker='^', label="Satellite (used)", + linewidth=0, color='#a61111', markersize=5), + Line2D([0], [0], marker='^', label="Satellite (unused)", + linewidth=0, color='red', markersize=5, fillstyle='none', markeredgewidth=0.5), + ), + loc='lower left', + fontsize='xx-small' + ) + + # Save final PDF figure + f.savefig(pdf_filename, bbox_inches='tight') diff --git a/satgenpy/satgen/post_analysis/print_routes_and_rtt_failure.py b/satgenpy/satgen/post_analysis/print_routes_and_rtt_failure.py new file mode 100644 index 000000000..6369a5a5e --- /dev/null +++ b/satgenpy/satgen/post_analysis/print_routes_and_rtt_failure.py @@ -0,0 +1,148 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from .graph_tools import * +from satgen.isls import * +from satgen.ground_stations import * +from satgen.tles import * +import exputil +import tempfile + + +def print_routes_and_rtt_failure(base_output_dir, satellite_network_dir, dynamic_state_update_interval_ms, + simulation_end_time_s, src, dst, satgenpy_dir_with_ending_slash, failure_table): + + # Local shell + local_shell = exputil.LocalShell() + + # Dynamic state dir can be inferred + satellite_network_dynamic_state_dir = "%s/dynamic_state_%dms_for_%ds" % ( + satellite_network_dir, dynamic_state_update_interval_ms, simulation_end_time_s + ) + + # Default output dir assumes it is done manual + pdf_dir = base_output_dir + "/pdf" + data_dir = base_output_dir + "/data" + local_shell.make_full_dir(pdf_dir) + local_shell.make_full_dir(data_dir) + + # Variables (load in for each thread such that they don't interfere) + ground_stations = read_ground_stations_extended(satellite_network_dir + "/ground_stations.txt") + tles = read_tles(satellite_network_dir + "/tles.txt") + satellites = tles["satellites"] + list_isls = read_isls(satellite_network_dir + "/isls.txt", len(satellites)) + epoch = tles["epoch"] + description = exputil.PropertiesConfig(satellite_network_dir + "/description.txt") + + # Derivatives + simulation_end_time_ns = simulation_end_time_s * 1000 * 1000 * 1000 + dynamic_state_update_interval_ns = dynamic_state_update_interval_ms * 1000 * 1000 + max_gsl_length_m = exputil.parse_positive_float(description.get_property_or_fail("max_gsl_length_m")) + max_isl_length_m = exputil.parse_positive_float(description.get_property_or_fail("max_isl_length_m")) + + # Write data file + + data_path_filename = data_dir + "/networkx_path_" + str(src) + "_to_" + str(dst) + ".txt" + with open(data_path_filename, "w+") as data_path_file: + + # For each time moment + fstate = {} # running fstate shared across all timesteps + current_path = [] + rtt_ns_list = [] + for t in range(0, simulation_end_time_ns, dynamic_state_update_interval_ns): + + with open(satellite_network_dynamic_state_dir + "/fstate_" + str(t) + ".txt", "r") as f_in: + for line in f_in: + spl = line.split(",") + current = int(spl[0]) + destination = int(spl[1]) + next_hop = int(spl[2]) + fstate[(current, destination)] = next_hop + + # Remove node failures from fstate + for failure in failure_table: + if failure_table[failure][0] <= t <= failure_table[failure][1]: + keys_to_update = [] + for key in fstate: + source, destination = key + next_hop = fstate[key] + if source == failure or destination == failure or next_hop == failure: + keys_to_update.append(key) + for key in keys_to_update: + fstate[key] = -1 + + # Calculate path length + path_there = get_path(src, dst, fstate) + path_back = get_path(dst, src, fstate) + if path_there is not None and path_back is not None: + length_src_to_dst_m = compute_path_length_without_graph(path_there, epoch, t, satellites, + ground_stations, list_isls, + max_gsl_length_m, max_isl_length_m) + length_dst_to_src_m = compute_path_length_without_graph(path_back, epoch, t, + satellites, ground_stations, list_isls, + max_gsl_length_m, max_isl_length_m) + rtt_ns = (length_src_to_dst_m + length_dst_to_src_m) * 1000000000.0 / 299792458.0 + else: + length_src_to_dst_m = 0.0 + length_dst_to_src_m = 0.0 + rtt_ns = 0.0 + + # Add to RTT list + rtt_ns_list.append((t, rtt_ns)) + + # Only if there is a new path, print new path + new_path = get_path(src, dst, fstate) + if current_path != new_path: + + # This is the new path + current_path = new_path + + # Write change nicely to the console + print("Change at t=" + str(t) + " ns (= " + str(t / 1e9) + " seconds)") + print(" > Path..... " + (" -- ".join(list(map(lambda x: str(x), current_path))) + if current_path is not None else "Unreachable")) + print(" > Length... " + str(length_src_to_dst_m + length_dst_to_src_m) + " m") + print(" > RTT...... %.2f ms" % (rtt_ns / 1e6)) + print("") + + # Write to path file + data_path_file.write(str(t) + "," + ("-".join(list(map(lambda x: str(x), current_path))) + if current_path is not None else "Unreachable") + "\n") + + # Write data file + data_filename = data_dir + "/networkx_rtt_" + str(src) + "_to_" + str(dst) + ".txt" + with open(data_filename, "w+") as data_file: + for i in range(len(rtt_ns_list)): + data_file.write("%d,%.10f\n" % (rtt_ns_list[i][0], rtt_ns_list[i][1])) + + # Make plot + pdf_filename = pdf_dir + "/time_vs_networkx_rtt_" + str(src) + "_to_" + str(dst) + ".pdf" + tf = tempfile.NamedTemporaryFile(delete=False) + tf.close() + local_shell.copy_file(satgenpy_dir_with_ending_slash + "plot/plot_time_vs_networkx_rtt.plt", tf.name) + local_shell.sed_replace_in_file_plain(tf.name, "[OUTPUT-FILE]", pdf_filename) + local_shell.sed_replace_in_file_plain(tf.name, "[DATA-FILE]", data_filename) + local_shell.perfect_exec("gnuplot " + tf.name) + print("Produced plot: " + pdf_filename) + local_shell.remove(tf.name) + + diff --git a/satgenpy/satgen/simulate_failures/__init__.py b/satgenpy/satgen/simulate_failures/__init__.py new file mode 100644 index 000000000..bf90929f4 --- /dev/null +++ b/satgenpy/satgen/simulate_failures/__init__.py @@ -0,0 +1 @@ +from .parse_failure_file import parse_failure_file \ No newline at end of file diff --git a/satgenpy/satgen/simulate_failures/parse_failure_file.py b/satgenpy/satgen/simulate_failures/parse_failure_file.py new file mode 100644 index 000000000..cdb24fad8 --- /dev/null +++ b/satgenpy/satgen/simulate_failures/parse_failure_file.py @@ -0,0 +1,29 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 ETH Zurich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +def parse_failure_file(failure_file): + failure_table = {} + with open(failure_file, "r") as f: + for line in f: + node_id, failure_start_time, failure_end_time = line.split(",") + failure_table[int(node_id)] = (int(float(failure_start_time) * 1_000_000_000), int(float(failure_end_time) * 1_000_000_000)) + return failure_table \ No newline at end of file