Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

External/feature/burn planner #237

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions TestFlightAPI/TestFlightAPI/TestFlightAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ internal static string UnitStringForMTBFUnitShort(MTBFUnits units)
return "-";
}
}

// Simply converts the failure rate to the reliability rate at time t.
public static float FailureRateToReliability(float failureRate, float t)
{
Expand Down Expand Up @@ -260,7 +260,7 @@ public static string GetPartName(string partName)
partName = partName.Replace(".", "-");
return partName.Replace("_", "-");
}

public static ITestFlightCore ResolveAlias(Part part, string alias)
{
// Look at each Core on the part and find the one that defines the given alias
Expand Down Expand Up @@ -324,7 +324,7 @@ public static List<PartModule> GetAllTestFlightModulesForPart(Part part)
}
return modules;
}


public static List<PartModule> GetAllTestFlightModulesForAlias(Part part, string alias)
{
Expand Down Expand Up @@ -610,6 +610,21 @@ public static T InvokeTool<T>(string methodName, object[] parameters)
public static string PartWithLeastData() => InvokeTool<string>("PartWithLeastData", null);
public static string PartWithNoData(string partList) => InvokeTool<string>("PartWithNoData", new object[] { partList });
public static TestFlightPartData GetPartDataForPart(string partName) => InvokeTool<TestFlightPartData>("GetPartDataForPart", new object[] { partName });

public static float Integrate(Func<float, float> f, float begin, float end, float stepSize = 0.5f)
{
float left = begin;
float prevValue = f(left);
float acc = 0f;
while (left < end)
{
left += stepSize;
float value = f(left);
acc += (prevValue + value) * stepSize * 0.5f;
prevValue = value;
}
return acc;
}
}

public struct TestFlightFailureDetails
Expand Down Expand Up @@ -722,7 +737,7 @@ string Configuration
/// </summary>
/// <returns>The current burn time for scope or 0 if this module does not track burn time.</returns>
float GetScopedRunTime(RatingScope ratingScope);

/// <summary>
/// Should return a string if the module wants to report any information to the user in the TestFlight Editor window.
/// </summary>
Expand Down Expand Up @@ -799,7 +814,7 @@ string Configuration
/// </summary>
/// <returns>A string of information to display to the user, or "" if none</returns>
string GetModuleInfo(string configuration);

void SetActiveConfig(string configuration);
}

Expand Down Expand Up @@ -985,4 +1000,3 @@ bool DebugEnabled
float GetTechTransfer();
}
}

6 changes: 6 additions & 0 deletions TestFlightCore/TestFlightCore/TestFlightCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ bool SetActiveConfigFromInterop()
currentConfig.TryGetValue("rndRate", ref rndRate);
currentConfig.TryGetValue("rndCost", ref rndCost);

// Invalidate cached value.
if (prevConfig != currentConfig)
{
baseFailureRate = 0f;
}

return prevConfig == currentConfig;
}

Expand Down
84 changes: 67 additions & 17 deletions TestFlightReliability_EngineCycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ public class TestFlightReliability_EngineCycle : TestFlightReliabilityBase
/// </summary>
[KSPField]
public FloatCurve continuousCycle;

/// <summary>
/// maximum rated cumulative run time of the engine over the entire lifecycle
/// </summary>
[KSPField]
[KSPField(guiName = "Rated burn time", groupName = "TestFlight_Plan", groupDisplayName = "TestFlight Planning", guiActiveEditor = true, guiUnits = "s")]
public float ratedBurnTime = 0f;

/// <summary>
/// maximum rated continuous burn time between restarts
/// </summary>
[KSPField]
[KSPField(guiName = "Rated continuous burn time", groupName = "TestFlight_Plan", groupDisplayName = "TestFlight Planning", guiUnits = "s")]
public float ratedContinuousBurnTime;

[KSPField]
public string engineID = "";
[KSPField]
Expand All @@ -59,13 +59,30 @@ public class TestFlightReliability_EngineCycle : TestFlightReliabilityBase
[KSPField(isPersistant = true)]
public double previousOperatingTime = 0d;

[KSPField(guiName = "Burn time", groupName = "TestFlight_Plan", groupDisplayName = "TestFlight Planning", guiActiveEditor = true, guiUnits = "s"),
UI_FloatRange(minValue = 0f, stepIncrement = 1f, scene = UI_Scene.Editor)]
public float plannerBurnTime;
[KSPField(guiName = "Continuous burn time", groupName = "TestFlight_Plan", groupDisplayName = "TestFlight Planning", guiActiveEditor = true, guiUnits = "s"),
UI_FloatRange(minValue = 1f, stepIncrement = 1f, scene = UI_Scene.Editor)]
public float plannerContinuousBurnTime;
[KSPField(guiName = "Survival chance", groupName = "TestFlight_Plan", groupDisplayName = "TestFlight Planning", guiActiveEditor = true, guiFormat = "P2")]
public float plannerSurvivalChance;

public override void OnStart(StartState state)
{
base.OnStart(state);
engine = new EngineModuleWrapper();
engine.InitWithEngine(this.part, engineID);
Fields[nameof(currentRunTime)].guiUnits = $"s/{ratedContinuousBurnTime}s";
Fields[nameof(engineOperatingTime)].guiUnits = $"s/{ratedBurnTime}s";

Fields[nameof(plannerBurnTime)].uiControlEditor.onFieldChanged += onPlannerChanged;
Fields[nameof(plannerBurnTime)].uiControlEditor.onSymmetryFieldChanged += onPlannerChanged;
Fields[nameof(plannerContinuousBurnTime)].uiControlEditor.onFieldChanged += onPlannerChanged;
Fields[nameof(plannerContinuousBurnTime)].uiControlEditor.onSymmetryFieldChanged += onPlannerChanged;
if (ratedBurnTime == ratedContinuousBurnTime) plannerBurnTime = ratedBurnTime; // Set the total burn time slider to the rated burn time only if there isn't a continuous burn time.
plannerContinuousBurnTime = ratedContinuousBurnTime;
UpdatePlanner();
}

public override double GetBaseFailureRate(float flightData)
Expand All @@ -85,23 +102,23 @@ protected void UpdateCycle()
if (!core.IsPartOperating())
{
// engine is not running

// reset continuous run time
currentRunTime = 0;
}
else
{
// engine is running

// increase both continuous and cumulative run times
currentRunTime += deltaTime; // continuous
engineOperatingTime += deltaTime; // cumulative


// calculate total failure rate modifier
float cumulativeModifier = cycle.Evaluate((float)engineOperatingTime);
float continuousModifier = continuousCycle.Evaluate((float)currentRunTime);

core.SetTriggerMomentaryFailureModifier("EngineCycle", cumulativeModifier * continuousModifier, this);
}
}
Expand All @@ -117,20 +134,20 @@ public override void OnUpdate()

UpdateCycle();

// We intentionally do NOT call our base class OnUpdate() because that would kick off a second round of
// failure checks which is already handled by the main Reliability module that should
// We intentionally do NOT call our base class OnUpdate() because that would kick off a second round of
// failure checks which is already handled by the main Reliability module that should
// already be on the part (This PartModule should be added in addition to the normal reliability module)
// base.OnUpdate();
}
public override void OnLoad(ConfigNode node)
{
base.OnLoad(node);
}

public override void SetActiveConfig(string alias)
{
base.SetActiveConfig(alias);

if (currentConfig == null) return;

// update current values with those from the current config node
Expand All @@ -141,7 +158,7 @@ public override void SetActiveConfig(string alias)
}
else
{
cycle.Add(0f,1f);
cycle.Add(0f, 1f);
}

continuousCycle = new FloatCurve();
Expand All @@ -164,6 +181,8 @@ public override void SetActiveConfig(string alias)
{
ratedContinuousBurnTime = ratedBurnTime;
}

UpdatePlanner();
}

public override string GetModuleInfo(string configuration, float reliabilityAtTime)
Expand Down Expand Up @@ -215,7 +234,7 @@ public override float GetRatedTime(string configuration, RatingScope ratingScope
return nodeBurnTime;
}
break;

case RatingScope.Continuous:
if (configNode.HasValue("ratedContinuousBurnTime"))
{
Expand All @@ -230,8 +249,8 @@ public override float GetRatedTime(string configuration, RatingScope ratingScope

return base.GetRatedTime(configuration, ratingScope);
}



public override float GetRatedTime(RatingScope ratingScope)
{
Expand Down Expand Up @@ -301,6 +320,37 @@ public override List<string> GetTestFlightInfo(float reliabilityAtTime)
}
return infoStrings;
}

protected void onPlannerChanged(BaseField f, object obj) => UpdatePlanner();

protected void UpdatePlanner()
{
if (core == null) return;

((UI_FloatRange)(Fields[nameof(plannerBurnTime)].uiControlEditor)).maxValue = cycle.maxTime;
plannerBurnTime = Mathf.Clamp(plannerBurnTime, 0f, cycle.maxTime);
((UI_FloatRange)(Fields[nameof(plannerContinuousBurnTime)].uiControlEditor)).maxValue = continuousCycle.maxTime;
plannerContinuousBurnTime = Mathf.Clamp(plannerContinuousBurnTime, 1f, continuousCycle.maxTime);

bool hasContinuousBurnTime = ratedBurnTime != ratedContinuousBurnTime;

Fields[nameof(ratedContinuousBurnTime)].guiActiveEditor = hasContinuousBurnTime;
Fields[nameof(plannerBurnTime)].guiName = hasContinuousBurnTime ? "Cumulative burn time" : "Burn time";
Fields[nameof(plannerContinuousBurnTime)].guiActiveEditor = hasContinuousBurnTime;

float burnTime = hasContinuousBurnTime ? plannerContinuousBurnTime : plannerBurnTime;

Func<float, float> instantaneousFailureRateMult = (t) =>
{
float mult = cycle.Evaluate(hasContinuousBurnTime ? t + plannerBurnTime : t);
if (hasContinuousBurnTime) mult *= continuousCycle.Evaluate(t);
return Mathf.Max(mult, 1f);
};

// Cumulative survival chance at time T = 1 - F(T) = $exp(-\int_0^T h(t)dt)$.
// Since the base failure rate is constant, we can pull it out of the integral.
plannerSurvivalChance = Mathf.Exp(-(float)core.GetBaseFailureRate()
* TestFlightUtil.Integrate(instantaneousFailureRateMult, 0f, burnTime));
}
}
}