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

Proposal for thermal control + various changes #105

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

gotmachine
Copy link
Contributor

@gotmachine gotmachine commented Apr 15, 2017

The PR contains a proposal for the thermal control system and various tweaks. Meant as a reference for future implementation

CHANGES :

Issue #81 : Being able to use different modifiers for input rate and degeneration rate in a rule

Added an optional "modifier_degen" field to rule, that allow to specify a separate set of modifiers for the degeneration rate.
I tried to check "modifiers_degen" in all places where rules modifiers existence was checked, but this may need to be reviewed.

Issue #114 : Temperature inconsistency between loaded / unloaded vessels

Sim.Temperature() now doesn't use the skinTemp of the vessel root part.

Issue #83 : Thermal control

The proposed system has the following features :

  • Simulate the thermal flux that affect the vessel habitat, accounting for :

    • Habitat surface
    • Heat gains from the solar + albedo + body + background flux
    • Heat gains from the kerbal bodies
    • Heat losses from radiative flux
    • Heat losses/gains due to conductive/convective transfers when in atmosphere
  • Simulate the temperature change in habitat accounting for thermal inertia

  • Kill the kerbals when the temperature change is too drastic

  • A ECLSS heater process use EC to compensate heat losses

  • Radiators produce a Coolant resource using EC to compensate heat gains

  • The Coolant output of radiators account for

    • Radiator surface
    • Radiator orientation
    • Radiative flux affected by coolant temperature (configurable by the player, higher temperature = more efficient and higher EC requirement
    • Solar, albedo, body flux, according to the radiator orientation
    • Atmospheric conductive/convective transfers when in atmosphere
  • Exothermic ECLSS/ISRU processes have a Coolant input, meaning that they require radiators to work

  • Related settings :

  SurvivalTemperature = 295.0         // ideal living temperature
  SurvivalRange = 25.0                // sweet spot around survival temperature
  SurvivalTime = 86400.0              // time of survival at SurvivalRange limit (s)
  HabSpecificHeat = 100000.0          // heat capacity of habitat (J/m^3/K)
  HabAbsorptivity = 0.15              // habitat surface absorptivity factor
  HabEmissivity = 0.1                 // habitat surface emissivity factor
  ExposedSurfaceFactor = 0.25         // % of habitat surface exposed to full flux intensity
  KerbalHeat = 30.0                   // Kerbal body heat production (W)
  • Radiator partmodule config :
  MODULE
  {
    name = Radiator
    input_resource = ElectricCharge   // input resource consumed to make the pump work
    input_rate_min = 0.005            // input per unit of coolant produced at min temperature, per second
    input_rate_max = 0.500            // input per unit of coolant produced at max temperature, per second
    output_resource = Coolant         // name of the coolant output resource  
    temperature_min = 290.0           // tweakable minimal coolant temperature (higher temperature = higher output rate)
    temperature_max = 340.0           // tweakable maximal coolant temperature (higher temperature = higher output rate)
    emissivity = 1.0                  // optional emissivity/, higher values = better output rate
    // surface = 0.0                  // surface of a single face of the radiator, autodetected excepted if this is a deployable radiator and the drag_cube isn't defined in the config
    // radiator_type = frontal        // must be specified for non-deployable radiators, valid values : frontal (ex : stock small radiator), radial (ex : stock "edge" radiator)
  }
  • New modifiers :
    • pos_flux / neg_flux : return the thermal flux in Watts for the vessel habitat.
    • pos_temp/neg_temp : a [0;1] representing how drastic is the temperature change in the habitat

Other modifiers changes / addition

  • Added a "mod_comfort_space_limiter" modifier, that must be used together with living_space and comfort modifiers. It alter comfort factor to act as a secondary bonus against living_space (try it !).
  • Added a "crew_count" modifier
  • Added a "inverse" modifier :
    • Return 1/other modifiers
    • Division by zero rule : return 0.0 if divided modifier equal 0.0
    • Order-dependant, will apply the inverse on previous modifiers :
      ex : modifier = volume,inverse,temperature // equal (1/volume)*temperature
  • The "temperature" modifier is removed

Planner changes

  • Environment section : temperature difference now show relative (signed) temperature instead of absolute temperature
  • Stress section better accommodate the absence of comfort / pressure modifiers, and modified the formula for time to breakdown to account for the eventual presence of an input in the living_space rule. This allow to create a stress rule where the degen accumulation reset when living_space is above ideal.
  • The comfort tooltip now doesn't show comfort factors who have their factor equal to 0.0 in the settings.cfg (this allow to customize which factors are used).

@gotmachine gotmachine closed this Apr 15, 2017
…ecked for

- Reworked STRESS section of the planner to better accomodate custom rules
@gotmachine gotmachine reopened this Apr 15, 2017
@gotmachine gotmachine closed this Apr 16, 2017
- changed temp_diff to return signed temperature, temperature modifier still absolute
@gotmachine gotmachine changed the title PR for issue #81 (optional separate modifier for degeneration rate in rule) PR for issue #81 + new modifiers + planner tweaks Apr 17, 2017
@gotmachine gotmachine reopened this Apr 17, 2017
@gotmachine gotmachine mentioned this pull request Apr 17, 2017
@@ -47,6 +55,10 @@ public static double evaluate(Vessel v, vessel_info vi, vessel_resources resourc
k /= vi.comforts.factor;
break;

case "mod_comfort_space_limiter":
k *= vi.comforts.factor * ((1 - vi.comforts.factor) + (vi.living_space));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary parenthesis, possible int to double conversion that can be avoided (don't trust the compiler)

k *= vi.comforts.factor * (1.0 - vi.comforts.factor + vi.living_space);

for (int i = 0; i < factors.Count; i++)
{
if (i > 1) tooltip_text += "\n";
tooltip_text += factors[i];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string operations generate a lot of temporaries
use StringBuilder directly instead

StringBuilder sb = new StringBuilder();
for(...)
{
  sb.append(factors[i]);
}
return sb.ToString();


case "inverse":
double i = 1.0 / k;
k = double.IsInfinity(i) || double.IsNaN(i) ? 0.0 : i;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace it with

k = k > double.epsilon ? 1.0 / k : 0.0;

@ShotgunNinja
Copy link
Owner

First of all, thanks a lot for your continuous contributions to this project.
I see that you are growing familiar with the codebase, and in general I like what you are doing here.
I just added a couple comments in the changes about minor performance issues.

Now, you are probably aware I'm about to refactor/rewrite a lot of stuff.
One of the potential things is the modifier, that I want to improve.

I already got a 'generic expression parser' done.
Its very fast, and the only limitation is that it doesn't support operator precedence.

So I was thinking to introduce the concept of a 'modifier-expression'.

  • it is a mathematical function that return a scalar value
  • it can access a set of 'environment variables'
  • it can access resource amount, capacity, level, rate
  • it can use basic unary and binary operators, sqrt, min/max/clamp and sin/cos
  • it can order operations using parenthesis

That way you can write modifier-expressions like:

modifier = max(env.temperature - 295, 5) - 5

to get temperature above survival range.
Note how the actual settings of survival range just become a part of the modifier.

Or you could write a modifier-expression like:

modifier = Atmosphere.rate > 0

that will evaluate to 1.0 when something is pressurizing the habitat, and 0.0 otherwise.

Or this one:

modifier = Atmosphere.level > 0.9

that is 1.0 if pressure is ~90 kPA, and 0.0 otherwise.

This new 'modifier-expressions' concept is also going to be used in two other (awesome) new things that are coming: a new telemetry system where arbitrary modifier-expression readings are provided by parts/rules, and a new alert system where arbitrary modifier-expression conditions are checked by parts/rules.

The new telemetry system in particular is going to be relevant here. You have already understood the fundamental impossibility of providing the current planner analysis panels in a generic rule-driven framework. Therefore the current planner panels are a sort of 'hack', good enough but show its limitations as soon as the user start specifying rules very different than the ones I developed against. I hope the new telemetry system may completely replace the planner panels too.

This is all very early-design / experiments. I'm really interested in what you think about these proposed improvements. Best!

@gotmachine gotmachine changed the title PR for issue #81 + new modifiers + planner tweaks PR for thermal control + various changes Apr 26, 2017
@gotmachine gotmachine changed the title PR for thermal control + various changes Proposal for thermal control + various changes Apr 26, 2017
@gotmachine
Copy link
Contributor Author

gotmachine commented Apr 26, 2017

Thanks for the reviewing of my code. I'm a hobbyist programmer and while I usually succeed in making things work, I'm really struggling to find the most efficient/fast/clean way of doing it, so your remarks are super useful.

All this is meant to be an implementation proposal, not a real PR. It would require a testing / fixing / optimization pass as well as a rebasing on your dev branch.

The modifier change seems a good idea, indeed having to maintain duplicates of functional code for flight and editor (planner) situations is not ideal. My tough on this when I discovered how all this work is that ideally, the "Vessel" reference in the "vessel_info" class should be allowed to be null. Then all the functions used to calculate the "vessel_info" properties should be able to perform with the actual vessel being optional.

This is what I did for the Habitat static function that calculate atmospheric flux and use the root part skinTemp when loaded to account for reentry heat. This allow me to use the same function in planner and vessel_info:
double Habitat.atmo_flux(CelestialBody mainBody, double altitude, double surface, double env_temperature, Vessel v = null)
That allow me to use it like that in vessel_info :
Habitat.atmo_flux(v.mainBody, v.altitude, surface, temperature, v)
And like that in the planner :
Habitat.atmo_flux(env.body, env.altitude, surface, env.temperature)
Then in the function, to illustrate :
some_value = (v == null) ? fallback_value_for_planner : value_when_loaded;

Unless I misunderstand what you are proposing, it seems to me that this is what you are trying to do : an unified vessel variables cache that would be indifferent to the game context (editor/flight loaded/flight unloaded). If this happen and together with your (awesome) "modifier-expression" idea, this could indeed allow a config-driven planner (and in-flight monitor) UI system. Another thing that I was thinking is that processes and rules should be merged in a single system. Maybe this is too much but here is some prospective work for an unified system, using habitat thermal control as an example :

Rules
{
  // a VesselValue is like a resource, excepted it isn't stored in a part but by vessel
  // when docking, the resulting vessel get the sum of the two vessels amount & capacity
  // the capacity modifier allow to determine factor to split the stored amount between the two vessels after undocking 
  VesselValue
  {
    name = neg_kjoule             // this can be used like a resource in modifier-expression (amount/capacity/level...)
    title = kJ                    // name of the value to display in the GUI    
    capacity = neg_kjoule_amount  // modifier used to calculate max amount of VesselValue, if unspecified capacity = amount at all times
  }
  
  Modifier
  {
    name = neg_kjoule_amount
    expression = env.volume * 100000.0 * 25.0 // min temperature is ideal temperature - 25 K
  }
  
  // heat losses from environment
  ValueInput
  {
    name = heat losses          // reference/id to use in the GUI
    value = neg_kjoule          // value to add
    rate = 1.0
    interval = 1.0
    modifier = watt_flux_neg
  }
  
  // heat gains from environment
  ValueOutput
  {
    name = heat gains
    value = neg_kjoule    
    rate = 1.0
    interval = 1.0
    modifier = watt_flux_pos
  }
  
  // calculate net thermal flux (kW) for the habitat (real formula would more complex)
  Modifier
  {
    name = net_flux
    expression = ((5,67E-08 * env.surface * env.temperature * env.temperature * env.temperature * env.temperature) - (5,67E-08 * env.surface * 0.5 * env.temperature * env.temperature * env.temperature * env.temperature ) + (env.crew_count * 30.0)) * 0.001
    // would be great to be able to do pow(env.temperature,4.0)
  }
  
  Modifier
  {
    name = watt_flux_pos
    expression = (net_flux > 0.0) * net_flux
  }
  
  Modifier
  {
    name = watt_flux_neg
    expression = (net_flux < 0.0) * net_flux * -1.0 // is abs(net_flux) possible ?
  }
  
  // used to determine the amount of ECLSS heater available, for use in the ProcessControler module
  // no capacity specified : capacity is ignored (made equal to the amount at all times)
  VesselValue
  {
    name = _heater               // this can be used like a resource in modifier-expression (amount/capacity/level...)
    title = ECLSS heater
  }
  
  // the heater should not remove more heat than the stored heat !
  Modifier
  {
    name = heater_output
    expression = min(neg_kjoule.amount, _heater.amount)
  }
  
  // ECLSS heater input
  ResourceInput
  {
    name = heater
    resource = ElectricCharge
    rate = 1.0
    modifier = heater_output
  }
  
  // ECLSS heater output
  ValueInput
  {
    name = heater
    value = neg_kjoule    
    rate = 1.0
    interval = 1.0
    modifier = heater_output
  }
  
  // a KerbalValue is like a resource, excepted it isn't stored in a part but by each Kerbal on the vessel
  KerbalValue
  {
    name = kerbal_neg_temp             // this can be used like a resource in modifier-expression (amount/capacity/level...)
    capacity = kerbal_neg_temp_amount  // modifier used to calculate the capacity
    ksc_reset = true                   // is the KerbalValue amount set to 0.0 when the kerbal get back to KSC
    vessel_reset = true                // is the KerbalValue amount set to 0.0 when the vessel the kerbal is in is modified
  }
  
  Modifier
  {
    name = kerbal_neg_temp_amount
    expression = 60 * 60 * 24   // 24 hours to death at min temperature
  }
  
  ValueInput
  {
    value = kerbal_neg_temp          
    rate = 1.0
    interval = 1.0
    modifier = hab_neg_temp_input
  }
  
  Modifier
  {
    name = hab_neg_temp_input
    expression = pow(neg_kjoule.level,5.0) 
  }
  
  ValueOuput
  {
    value = kerbal_neg_temp    
    rate = 1.0
    interval = 1.0
    modifier = hab_pos_temp_output
  }
  
  Modifier
  {
    name = hab_neg_temp_output
    expression = neg_kjoule.level < 0.01 
  }

  Event
  {
    text = $ON_VESSEL$KERBAL froze to death   // the text to show on screen
    timewarp_break = true                     // does the event stop timewarp
    unfocused_show = true                     // does the text show up if this isn't the current vessel
    modifier = neg_temp_death                 // modifier-expression that trigger the event when turning from false to true
    consequence = death                       // death/breakdown/none
  }
  
  Modifier
  {
    name = pos_temp_death
    expression = kerbal_pos_temp.amount == kerbal_pos_temp.capacity
  }
  
  Modifier
  {
    name = watt_flux_pos
    expression = info.tempertature * info.
  }
  
  PlannerPanel
  {
    title = Thermal control                 // name of the panel
    section = 3                             // where to put it in the planner window
  }
  
  PlannerLine
  {
    panel = Thermal control                 // the line should be in this panel
    title = Environment flux (kW)           // the line left text
    value = net_flux                        // the line right text, modifier or use "" to evaluate as a string
  }
  
  PlannerLine
  {
    panel = Thermal control      // the line should be in this panel
    title = death by freezing    // the line left text
    value = freezing_time        // the line right text, modifier or use "" to evaluate as a string
    value_format = duration      // returned value is Lib.HumanReadableDuration(value)
    color = #ff0000              // color of the value
    visible = freezing_isvisible // modifier-expression that determine visibility
  }
  
  PlannerLine
  {
    panel = Thermal control        // the line should be in this panel
    title = temperature is stable  // the line left text
    color = #00ff00                // color of the value
    visible = tempstable_isvisible // modifier-expression that determine visibility
  }
  
  Modifier
  {
    name = freezing_time
    expression = (neg_kjoule.capacity / neg_kjoule.rate) + (60 * 60 * 24)
  }
  
  Modifier
  {
    name = freezing_isvisible
    expression = neg_kjoule.rate > 0.0
  }
  
  Modifier
  {
    name = tempstable_isvisible
    expression = (neg_kjoule.rate == 0.0) && (pos_kjoule.rate == 0.0)
  }

Another GUI example for stress :

Rule
{
  ...

  Event
  {
    text = The crew is feeling gravity again // the text to show on screen
    timewarp_break = false                   // does the event stop timewarp
    unfocused_show = false                   // does the text show up if this isn't the current vessel
    modifier = gravity_message               // modifier-expression that trigger the alert when turning from false to true
    consequence = none                       // death/breakdown/none
  }
  
  Modifier
  {
    name = gravity_message
    expression = env.firm_ground == 1.0
  }
  
  PlannerLine
  {
    title = breakdown                       // the line left text
    value = 1.0 / (degen modifier formula)  // the line right text, modifier-expression or use "" to evaluate as a string
    value_format = duration                 // returned value is Lib.HumanReadableDuration(value)
    visible = info.comfort < 0.2            // modifier-expression that determine visibility
  }
  PlannerLine
  {
    title = comfort              // the line left text
    value = "poor"               // the line right text, modifier-expression or use "" to evaluate as a string
    color = #ff0000              // color of the value
    visible = info.comfort < 0.2 // modifier-expression that determine visibility
  }
  PlannerLine
  {
    title = comfort
    value = "modest"
    color = #ff8300
    visible = (info.comfort >= 0.2) && (info.comfort < 0.4)
  }
  ...
  PlannerTooltip
  {
    line_title = comfort      // show tooltip on Rule_UI_line whose title = comfort
    title = firm ground       // the line left text
    value = info.firm_ground  // the line right text, modifier-expression or use "" to evaluate as a string
    value_format = boolean    // returned value is Lib.HumanReadableBoolean(bool value) {return value ? "true" : "false";}
    color = #00ff00           // color of the value
  }
  ...
}

@ShotgunNinja
Copy link
Owner

@gotmachine That's very good ideas and I feel the need to speed-update you on what's going on, so we can design this together. I'll create a new issue with the proposed changes to the mod architecture and we'll discuss there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants