|
| 1 | +# This implements the Venus GRAM in STK under the GRAM common framework. |
| 2 | +# This plugin was most recently tested with GRAM 2.1.0. |
| 3 | +# This plugin requires the built GRAMpy module to interface with the GRAM suite. |
| 4 | +# A copy of the GRAM Suite can be obtained through the NASA Technology Transfer Program. |
| 5 | + |
| 6 | +import typing, math |
| 7 | +from agi.stk12.plugins.utplugin import AgEUtLogMsgType |
| 8 | +from agi.stk12.plugins.attrautomation import AgEAttrAddFlags |
| 9 | +from agi.stk12.plugins.stkplugin import AgStkPluginSite |
| 10 | +from agi.stk12.vgt import AgECrdnSmartEpochState |
| 11 | +from GRAMpy import gram |
| 12 | + |
| 13 | +class CAgAsDensityModelPlugin(object): |
| 14 | + def __init__(self): |
| 15 | + # Debug flags |
| 16 | + self.DebugMode = False |
| 17 | + self.PluginEnabled = True |
| 18 | + self.MessageInterval = 1000 |
| 19 | + self.MsgCntr = -1 |
| 20 | + self.IntegSteps = 0 |
| 21 | + |
| 22 | + # INPUTS |
| 23 | + self.CentralBodyName = 'Venus' |
| 24 | + self.LowestValidAltKm = 0 |
| 25 | + self.SpicePath = r'\SPICE' |
| 26 | + self.InitialRandomSeed = 1001 |
| 27 | + self.DensityPerturbationScale = 1.0 |
| 28 | + self.MinimumRelativeStepSize = 0.0 |
| 29 | + self.FastModeOn = 0 |
| 30 | + self.DensityType = 'Mean' |
| 31 | + |
| 32 | + # STK specific, do not change |
| 33 | + self.Scope = None |
| 34 | + self.Site = None |
| 35 | + self.Root = None |
| 36 | + self.Density = None |
| 37 | + self.DisplayName = 'VenusGRAM Suite Atmosphere Plugin' |
| 38 | + self.OutputLowAltMsg = True |
| 39 | + self.ComputingTemperature = True |
| 40 | + self.ComputingPressure = True |
| 41 | + |
| 42 | + # GRAM specific, do not change |
| 43 | + self.venus = None |
| 44 | + self.position = None |
| 45 | + |
| 46 | + def Init(self, Site:"IAgUtPluginSite") -> bool: |
| 47 | + self.Site = AgStkPluginSite(Site) |
| 48 | + self.Root = self.Site.StkRootObject |
| 49 | + |
| 50 | + # Parse scenario epoch |
| 51 | + self.Root.UnitPreferences.SetCurrentUnit('DateFormat', 'YYYY:MM:DD') # YYYY:MM:DD:HH:MM:SS.sss Time |
| 52 | + smartEpoch = self.Root.CurrentScenario.AnalysisEpoch |
| 53 | + if smartEpoch.State == AgECrdnSmartEpochState.eCrdnSmartEpochStateExplicit: |
| 54 | + epoch = smartEpoch.TimeInstant |
| 55 | + else: |
| 56 | + epoch = smartEpoch.ReferenceEvent.FindOccurrence().Epoch |
| 57 | + epochSplit = epoch.split(':') |
| 58 | + self.Root.UnitPreferences.SetCurrentUnit('DateFormat', 'UTCG') |
| 59 | + |
| 60 | + # Set up GRAM |
| 61 | + inputParameters = gram.VenusInputParameters() |
| 62 | + inputParameters.timeFrame = gram.PET |
| 63 | + inputParameters.spicePath = self.SpicePath |
| 64 | + inputParameters.initialRandomSeed = self.InitialRandomSeed |
| 65 | + inputParameters.densityPerturbationScale = self.DensityPerturbationScale |
| 66 | + inputParameters.minRelativeStepSize = self.MinimumRelativeStepSize |
| 67 | + inputParameters.fastModeOn = self.FastModeOn |
| 68 | + self.venus = gram.VenusAtmosphere() |
| 69 | + self.venus.setInputParameters(inputParameters) |
| 70 | + |
| 71 | + # Configure GRAM start time and position |
| 72 | + self.position = gram.Position() |
| 73 | + ttime = gram.GramTime() |
| 74 | + ttime.setStartTime(int(epochSplit[0]), int(epochSplit[1]), int(epochSplit[2]), int(epochSplit[3]), int(epochSplit[4]), float(epochSplit[5]), gram.UTC, gram.PET) |
| 75 | + self.venus.setStartTime(ttime) |
| 76 | + |
| 77 | + if self.DebugMode: |
| 78 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgDebug, "VenusGRAMPlugin:Init()") |
| 79 | + if self.PluginEnabled: |
| 80 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgInfo, "VenusGRAMPlugin:Init() Enabled") |
| 81 | + else: |
| 82 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgInfo, "VenusGRAMPlugin:Init() Disabled because Enabled flag is False") |
| 83 | + if not self.PluginEnabled: |
| 84 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgAlarm, "VenusGRAMPlugin:Init() Disabled because Enabled flag is False") |
| 85 | + return self.PluginEnabled |
| 86 | + |
| 87 | + def Register(self, Result:"IAgAsDensityModelResultRegister"): |
| 88 | + if self.DebugMode: |
| 89 | + Result.Message(AgEUtLogMsgType.eUtLogMsgDebug, "VenusGRAMPlugin:Register()") |
| 90 | + |
| 91 | + def Free(self) -> bool: |
| 92 | + self.Site = None |
| 93 | + return True |
| 94 | + |
| 95 | + # PreCompute is a member of the optional IAgAsDensityModelPluginExtended interface |
| 96 | + def PreCompute(self, Result:"IAgAsDensityModelResult") -> bool: |
| 97 | + self.IntegSteps = 0 |
| 98 | + self.MsgCntr = -1 |
| 99 | + if self.PluginEnabled: |
| 100 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgDebug, "PreCompute() called.") |
| 101 | + |
| 102 | + if self.DebugMode: |
| 103 | + self.TestResultInterface("PreCompute()", Result) |
| 104 | + |
| 105 | + return True |
| 106 | + return False |
| 107 | + |
| 108 | + # PreNextStep is a member of the optional IAgAsDensityModelPluginExtended interface |
| 109 | + def PreNextStep(self, Result:"IAgAsDensityModelResult") -> bool: |
| 110 | + if self.PluginEnabled: |
| 111 | + RptCnt = self.MessageInterval |
| 112 | + if self.DebugMode: |
| 113 | + if self.IntegSteps % RptCnt == 0: |
| 114 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgDebug, f'PreNextStep(): Integration Step: {self.IntegSteps}') |
| 115 | + if self.IntegSteps == 0: |
| 116 | + self.TestResultInterface("PreNextStep()", Result) |
| 117 | + |
| 118 | + self.IntegSteps += 1 |
| 119 | + |
| 120 | + return True |
| 121 | + return False |
| 122 | + |
| 123 | + # PostCompute is a member of the optional IAgAsDensityModelPluginExtended interface |
| 124 | + def PostCompute(self, Result:"IAgAsDensityModelResult") -> bool: |
| 125 | + if self.PluginEnabled: |
| 126 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgDebug, "PostCompute() called.") |
| 127 | + if self.DebugMode: |
| 128 | + self.TestResultInterface("PostCompute()", Result) |
| 129 | + |
| 130 | + return True |
| 131 | + return False |
| 132 | + |
| 133 | + def Evaluate(self, ResultEval:"IAgAsDensityModelResultEval") -> bool: |
| 134 | + self.MsgCntr += 1 |
| 135 | + if self.PluginEnabled: |
| 136 | + self.PluginEnabled = self.SetDensity(ResultEval) |
| 137 | + if self.DebugMode and self.MsgCntr == 0: |
| 138 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgDebug, "Evaluate() called.") |
| 139 | + self.TestResultEvalInterface("Evaluate()", ResultEval) |
| 140 | + |
| 141 | + return self.PluginEnabled |
| 142 | + |
| 143 | + def CentralBody(self) -> str: |
| 144 | + return self.CentralBodyName |
| 145 | + |
| 146 | + def ComputesTemperature(self) -> bool: |
| 147 | + return self.ComputingTemperature |
| 148 | + |
| 149 | + def ComputesPressure(self) -> bool: |
| 150 | + return self.ComputingPressure |
| 151 | + |
| 152 | + def UsesAugmentedSpaceWeather(self) -> bool: |
| 153 | + return False |
| 154 | + |
| 155 | + def GetLowestValidAltitude(self) -> bool: |
| 156 | + return self.LowestValidAltKm |
| 157 | + |
| 158 | + def OverrideAtmFluxLags(self, FluxLags: "IAgAsDensityModelPLuginAtmFluxLagsConfig") -> bool: |
| 159 | + return True |
| 160 | + |
| 161 | + def SetDensity(self, ResultEval:"IAgAsDensityModelResultEval") -> bool: |
| 162 | + # Query STK for time and LLA |
| 163 | + stkTime = float(ResultEval.DateString('EpSec')) |
| 164 | + LLA = ResultEval.LatLonAlt_Array() |
| 165 | + LatitudeDeg = math.degrees(LLA[0]) |
| 166 | + LongitudeDeg = math.degrees(LLA[1]) |
| 167 | + AltitudeKm = LLA[2] / 1000 |
| 168 | + |
| 169 | + # Validate altitude |
| 170 | + if AltitudeKm < self.LowestValidAltKm: |
| 171 | + if self.OutputLowAltMsg: |
| 172 | + msg = f'setDensity: altitude {AltitudeKm:1.6f} is less than minimum valid altitude ( {self.LowestValidAltKm} km ). Keeping density constant below this height.' |
| 173 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgWarning, f'{msg}') |
| 174 | + self.OutputLowAltMsg = False |
| 175 | + AltitudeKm = self.LowestValidAltKm |
| 176 | + |
| 177 | + if self.DebugMode: |
| 178 | + if self.MsgCntr % self.MessageInterval == 0: |
| 179 | + msg = f'setDensity: time = {stkTime:02.6f} EpSec' |
| 180 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgDebug, f'{msg}') |
| 181 | + msg = f'setDensity: lat = {LatitudeDeg:1.6f}, lon = {LongitudeDeg:1.6f}, alt = {AltitudeKm:1.6f}' |
| 182 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgDebug, f'{msg}') |
| 183 | + |
| 184 | + # Set GRAM position |
| 185 | + self.position.elapsedTime = stkTime |
| 186 | + self.position.latitude = LatitudeDeg |
| 187 | + self.position.longitude = LongitudeDeg |
| 188 | + self.position.height = AltitudeKm |
| 189 | + self.venus.setPosition(self.position) |
| 190 | + |
| 191 | + # Update GRAM atmosphere |
| 192 | + self.venus.update() |
| 193 | + atmos = self.venus.getAtmosphereState() |
| 194 | + if self.DensityType == 'Mean': |
| 195 | + self.Density = atmos.density |
| 196 | + elif self.DensityType == 'Low': |
| 197 | + self.Density = atmos.lowDensity |
| 198 | + elif self.DensityType == 'High': |
| 199 | + self.Density = atmos.highDensity |
| 200 | + else: |
| 201 | + self.Density = atmos.perturbedDensity |
| 202 | + |
| 203 | + # Push result back to STK |
| 204 | + ResultEval.SetDensity(self.Density) |
| 205 | + ResultEval.SetPressure(atmos.pressure) |
| 206 | + ResultEval.SetTemperature(atmos.temperature) |
| 207 | + |
| 208 | + if self.DebugMode: |
| 209 | + if self.MsgCntr % self.MessageInterval == 0: |
| 210 | + msg = f'setDensity: density {self.Density}' |
| 211 | + self.Site.Message(AgEUtLogMsgType.eUtLogMsgDebug, f'{msg}') |
| 212 | + return True |
| 213 | + |
| 214 | + def TestResultInterface(self, Name:"str", Result:"IAgAsDensityModelResult"): |
| 215 | + if not self.DebugMode: |
| 216 | + return |
| 217 | + return |
| 218 | + |
| 219 | + def TestResultEvalInterface(self, Name:"str", ResultEval:"IAgAsDensityModelResultEval"): |
| 220 | + if not self.DebugMode: |
| 221 | + return |
| 222 | + return |
| 223 | + |
| 224 | + def GetPluginConfig(self, pAttrBuilder:"IAgAttrBuilder") -> typing.Any: |
| 225 | + if self.Scope is None: |
| 226 | + self.Scope = pAttrBuilder.NewScope() |
| 227 | + pAttrBuilder.AddStringDispatchProperty( self.Scope, "CentralBodyName", "CentralBodyName", "CentralBodyName", AgEAttrAddFlags.eAddFlagReadOnly) |
| 228 | + pAttrBuilder.AddStringDispatchProperty( self.Scope, "SpicePath", "SpicePath", "SpicePath", AgEAttrAddFlags.eAddFlagNone ) |
| 229 | + pAttrBuilder.AddIntMinMaxDispatchProperty( self.Scope, "InitialRandomSeed", "InitialRandomSeed", "InitialRandomSeed", 1, 29999, AgEAttrAddFlags.eAddFlagNone ) |
| 230 | + pAttrBuilder.AddDoubleMinMaxDispatchProperty( self.Scope, "DensityPerturbationScale", "DensityPerturbationScale", "DensityPerturbationScale", 0.0, 2.0, AgEAttrAddFlags.eAddFlagNone ) |
| 231 | + pAttrBuilder.AddDoubleMinMaxDispatchProperty( self.Scope, "MinimumRelativeStepSize", "MinimumRelativeStepSize", "MinimumRelativeStepSize", 0.0, 1.0, AgEAttrAddFlags.eAddFlagNone ) |
| 232 | + pAttrBuilder.AddBoolDispatchProperty( self.Scope, "FastModeOn", "FastModeOn", "FastModeOn", AgEAttrAddFlags.eAddFlagNone ) |
| 233 | + pAttrBuilder.AddChoicesDispatchProperty( self.Scope, "DensityType", "Density Type", "DensityType", ["Low", "Mean", "High", "Perturbed"] ) |
| 234 | + return self.Scope |
| 235 | + |
| 236 | + def VerifyPluginConfig(self, pPluginCfgResult:"IAgUtPluginConfigVerifyResult") -> bool: |
| 237 | + pPluginCfgResult.Result = True |
| 238 | + pPluginCfgResult.Message = "Ok" |
| 239 | + return True |
| 240 | + |
0 commit comments