<jupyter_start><jupyter_text>Constellation and Chain Analysis: MultiConstellation Multihop MultiPath**Terminology*** Node = Object in STK* Edge = Access between two objects in STK* Strand = The sequence of nodes and edges to complete access in a chain**This notebook shows how to:*** Find the shortest path between a starting and ending constellation, with many potential intermediate constellations.* A chain will be built between each sequential pair in the constellationOrder list. Then networkx will be used build the network with the nodes coming from the constellations and the connections between the nodes coming from the chain accesses. Multiple sublists can be passed into constellationOrderList.* Typical STK constraints such as range, link duration, Eb/No, etc are taken into account* Data in the df variable can be pushed back into STK as a user supplied variable, a strand can be shown using object lines, and active objects over the analysis time period or at a time instance can be turned on.**To reduce the runtime on subsequent runs, this scripts saves various computations to binary files and are reused for subsequent runs.** * The strands from the chains will be saved in the SavedNodes folder* the nodes and associated time delays will be saved in the SavedNodes folder* the node positions over time are saved in the SavedPositions folder* the accesses between nodes over time are saved in the SavedEdges folder. * These folders will be created as subfolders of the directory used to run the script.* During the first run the files will automatically be built and saved, subsequent runs will reload these files.* To make changes simply delete the associated .pkl file for any changed strands, nodes, etc. and the script will recompute the data as needed. Or force all of the data to be overridden by setting the override options to be True.<jupyter_code>import numpy as np
import pandas as pd
pd.set_option('max_colwidth', 120)
from comtypes.client import CreateObject
from comtypes.client import GetActiveObject
from comtypes.gen import STKObjects
from comtypes.gen import STKUtil
from comtypes.gen import AgSTKVgtLib
import seaborn as sns
import matplotlib.pyplot as plt
from chainPathLib2 import *
import time
import networkx as nx
folders = ['SavedNodes','SavedPositions','SavedStrands','SavedEdges','SavedNetworkData']
for folder in folders:
    if not os.path.exists(folder):
        os.makedirs(folder)<jupyter_output><empty_output><jupyter_text>Constellation Connection Order, Computation Time, Metric, Saved Data Options<jupyter_code># The constellationOrderLists builds a directed chain for each adjacent pair in the list
# i.e.: constellationOrderLists = [[A,B],[X,Y,Z,X]] would build chainAB,chainXY,chainYZ,chainZX
constellationOrderLists = [['Targets','ObservingSatsFORs','ObservingSats','ObservingSatsTransmitters','ObservingSatsReceivers','ObservingSats','RelaySatsFORs','RelaySats','RelaySatsFORs','EndLocations'],['RelaySats','RelaySats']]   

startingConstellation = 'Targets' # complete path starting constellation
endingConstellation = 'EndLocations' # complete path ending constellation

start = 0 # EpSec
stop = 800*10 # EpSec
metric = 'distance' # 'distance' or 'timeDelay' or 'bandwidth', custom metrics could be added
nodeDelays = {'ObservingSatsFORs':0.01,'ObservingSatsTransmitters':0.005,'ObservingSatsReceivers':0.005,'RelaySatsFORs':0.01,'RelaySats':0.002} # Add in time delays. Provide the constellation name in STK and the node delays
nodeDelays = {}
                           
stkVersion = 12
overrideStrands = False # Override previously computed chains
overrideNodeDelaysByNode = False # Override previously built node delay dictionaries
overrideNodesTimesPos = False # Override previously built node positions
overrideNetwork = False # Override previously built node positions
# Connect to STK
stkApp = GetActiveObject('STK{}.Application'.format(stkVersion))
stkRoot = stkApp.Personality2
stkRoot.Isolate()
stkRoot.UnitPreferences.SetCurrentUnit('DateFormat','EpSec') # Units to EpSec for ease of use
stkRoot.ExecuteCommand('Units_SetConnect / Date "EpochSeconds"');
try:
    stkRoot.ExecuteCommand('VO * ObjectLine DeleteAll'); # Clean up old object lines
except:
    pass

# Build chains and create a dict of time delays for each node
t1 = time.time()
chainNames = createDirectedChains(stkRoot,constellationOrderLists,start=start,stop=stop,color=12895232)   
print(time.time()-t1)
t1 = time.time()
nodeDelaysByNode = getNodeDelaysByNode(stkRoot,nodeDelays,chainNames=chainNames,overrideData=overrideNodeDelaysByNode)
print(time.time()-t1)<jupyter_output>1.5440022945404053
0.0040035247802734375<jupyter_text>Compute  Strands and Distances<jupyter_code># Compute strands, (this is actually a list of all intervals for each edge)
t1 = time.time()
strands,dfStrands = getAllStrands(stkRoot,chainNames,start,stop,overrideData=overrideStrands)
print(time.time()-t1)
dfStrands
# Compute node positions, distances and time delays

# Time resolution of distance/time computation
step = 10 # sec

t1 = time.time()
nodesTimesPos = computeNodesPosOverTime(stkRoot,strands,start,stop,step,overrideData=overrideNodesTimesPos) # Pull node position over time
t2 = time.time()
print(t2-t1)

t1 = time.time()
strandsAtTimes = getStrandsAtTimes(strands,start,stop,step) # Discretize strand intervals into times
t2 = time.time()
print(t2-t1)

# Check if data needed for strandsAtTimes is missing
t1 = time.time()
strands,dfStrands,nodesTimesPos,strandsAtTimes = recomputeMissingData(strands,dfStrands,nodesTimesPos,strandsAtTimes,recomputeIfDataIsMissing=True)
t2 = time.time()
print(t2-t1)

t1 = time.time()
timeNodePos = computeTimeNodePos(strandsAtTimes,nodesTimesPos) # Nodes and positions at each time
t2 = time.time()
print(t2-t1)

t1 = time.time()
timesEdgesDistancesDelays = computeTimeEdgesDistancesDelays(strandsAtTimes,nodesTimesPos,nodeDelaysByNode,overrideData=True)  # Edges, distances and delays at each time
t2 = time.time()
print(t2-t1)<jupyter_output>1.1439990997314453
3.9850406646728516
0.7817478179931641
9.990900754928589<jupyter_text>Use NX for Network Metrics and Reliability Analysis<jupyter_code># Get pairs of each starting and ending node permutation in the constellations
startingNodes = getNodesFromConstellation(stkRoot,startingConstellation)
endingNodes = getNodesFromConstellation(stkRoot,endingConstellation)
nodePairs = [(start,end) for start,end in itertools.product(startingNodes, endingNodes)] # full permutation
pd.DataFrame(nodePairs)
# Loop through each node pair and compute network metrics
# Edit computeNetworkMetrics in chainPathLibCustom to for additional metrics
for nodePair in nodePairs:
    df = computeNetworkMetrics(start,stop,step,timeNodePos,timesEdgesDistancesDelays,[nodePair[0]],[nodePair[1]],metric,computeNumNodesToLoseAccessBetweenAnyPair=True,overrideData=overrideNetwork,printTime=True,diNetwork=True)
df<jupyter_output><empty_output><jupyter_text>Investigate Routing between Specific Nodes<jupyter_code># Pick a starting and ending node
startingNode = 'Target/Target7'
endingNode = 'Place/Washington_DC'
# Load df
filename = 'SavedNetworkData/df{}{}.pkl'.format(startingNode.split('/')[-1],endingNode.split('/')[-1])
with open(filename,'rb') as f:
    df = pickle.load(f)
df = addLightAndNodeDelays(df,timesEdgesDistancesDelays)    
dfIntervals = createDfIntervals(df,stop,step)
addStrandsAsObjectLines(stkRoot,dfIntervals,color='yellow')
# Add data back into STK for reporting and plotting
t1 = time.time()
df['distance'] = df['distance']*1000 # May need to fix meter/kilometer issue
addDataToSTK(stkRoot,chainNames[0],df) # Adds data in df back into STK to the first chain under User Supplied data
print(time.time()-t1)
df
dfIntervals['strand'][0]
# Active objects in the network over time
objPaths = list(set((item for sublist in df['strand'] for item in sublist)))
# Turn on the objects in the scenario
turnGraphicsOnOff(stkRoot,objPaths,onOrOff = 'On',parentsOnly = True)
# Turn off the objects in the sceario
turnGraphicsOnOff(stkRoot,objPaths,onOrOff = 'Off',parentsOnly = True)<jupyter_output><empty_output><jupyter_text>Investigate Instances in Time<jupyter_code># Look at an instance in time (pick a time in df)
t = 0
stkRoot.CurrentTime = t
# Look at strand order and the node delay
objPaths = df['strand'][t/step]
nodeDelaysByStrand = {node:nodeDelaysByNode[node] for node in objPaths}
pd.DataFrame([*nodeDelaysByStrand.items()],columns=['node','nodeDelay'])
#look at possible connections for the object of interest at that time
nodeInterest = objPaths[0]
possibleNodeConnections(t,nodeInterest,timesEdgesDistancesDelays)
# Turn on the objects in the scenario
turnGraphicsOnOff(stkRoot,objPaths,onOrOff = 'On',parentsOnly = False)
# Turn off the objects in the sceario
turnGraphicsOnOff(stkRoot,objPaths,onOrOff = 'Off',parentsOnly = False)<jupyter_output><empty_output><jupyter_text>Investigate Node Utilization<jupyter_code># Most frequnt node in the shortest path and the sum of their durations
strands = dfIntervals[['strand','start','stop']].values
dfNodesIntervals = getNodesIntervalsFromStrands(strands)
dfNodeActive = getActiveDuration(dfNodesIntervals,start,stop)
dfNodeActive.sort_values('sum dur',ascending=False).head(10)<jupyter_output><empty_output><jupyter_text>Compute the Top N Paths for Multiple Node Pairs<jupyter_code># Loop through each node pair and compute network metrics for top N strands, assumes only 1 starting and ending node for each network
# The strands will all be unique, but large portions of the strand may be reused unless the nodes are edges are removed
topN = 5
removeUsedNodes = False # Remove any nodes in previous shortest paths
removeUsedEdges = False # Remove any edge in previous shortest paths
for nodePair in nodePairs:
    startingNode = nodePair[0]
    endingNode = nodePair[1]
    dfTopN = computeNetworkTopN(start,stop,step,timeNodePos,timesEdgesDistancesDelays,startingNode,endingNode,metric,topN=topN,overrideData=overrideNetwork,printTime=True,filename='',removeUsedNodes=removeUsedNodes,removeUsedEdges=removeUsedEdges)<jupyter_output>0.003000974655151367
0.0009965896606445312
0.00099945068359375
0.0010066032409667969
0.0009975433349609375
0.0030028820037841797
0.0016481876373291016
0.0019989013671875
0.001998424530029297
0.0019986629486083984
0.0020008087158203125
0.00099945068359375
0.00099945068359375
0.0010018348693847656<jupyter_text>Load And Merge Multiple Networks<jupyter_code>nodePairsToLoad = [
    ('Target/Target1', 'Place/Los_Angeles_CA'),
    ('Target/Target1', 'Place/Washington_DC'),
    ('Target/Target2', 'Place/Los_Angeles_CA'),
    ('Target/Target2', 'Place/Washington_DC')] # Can specifically give pairs of interest
nodePairsToLoad = nodePairs # Use all of the nodePairs
nodePairsToLoad
# Only load needed times
neededTimes = np.array(list(timesEdgesDistancesDelays.keys()))

# # Load multiple networks into one
# df = loadNetworkDfTopN(nodePairsToLoad,topN,neededTimes=neededTimes)
# df = addLightAndNodeDelays(df,timesEdgesDistancesDelays)    
# df

# Load multiple networks into one
df = loadNetworkDf(nodePairsToLoad,neededTimes=neededTimes)
df = addLightAndNodeDelays(df,timesEdgesDistancesDelays)    
df
# Load the paths into STK, there are some basic options for how to color the network

# Create intervals for each edge and count the number of times the edge is active
timesEdgeCountAll = createTimesEdgesCountFromDF(df)

# # Consider binning the counts to prevent a ton of unique colors, which can noteably speed up time to add data to STK
# # Look at the pd.cut documentation for specifying specific bin edges
# # Alternatively it is possible to color by a custom dataset for each edge, such as approx timeDelay of the edge, this would require custom editting
# numBins = 5
# bins = pd.cut(timesEdgeCountAll[:,2],numBins,labels=False)
# timesEdgeCountAll[:,2] = bins

t1 = time.time()
addTimesEdgesCountAsObjectLines(stkRoot,timesEdgeCountAll,step,addTo2D=False,color='%000196196',useColorRamp=True,rampColor1=[255,255,255],rampColor2=[255,0,0])
time.time()-t1<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08272043632757624, 'fraction_numerical': 0.0409883480704074, 'mean_word_length': 6.988118811881188, 'pattern_counts': {'":': 0, '<': 34, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '235952', 'license': 'Adobe-2006', 'n_tokens_mistral': 3989, 'n_tokens_neox': 3634, 'n_words': 1116, 'path': 'StkAutomation/Python/Scenario_Analysis/ConstellationAndNetworkRouting/MultiConstellationMultiHopMultiPath.ipynb', 'repo_name': 'jeremy-fields/STKCodeExamples', 'size': '12101'}