|
|
|
@@ -534,11 +534,19 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
|
|
|
|
|
if(.) //If we changed the mix to any degree
|
|
|
|
|
garbage_collect()
|
|
|
|
|
|
|
|
|
|
///Takes the amount of the gas you want to PP as an argument
|
|
|
|
|
///So I don't have to do some hacky switches/defines/magic strings
|
|
|
|
|
///eg:
|
|
|
|
|
///Plas_PP = get_partial_pressure(gas_mixture.plasma)
|
|
|
|
|
///O2_PP = get_partial_pressure(gas_mixture.oxygen)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Takes the amount of the gas you want to PP as an argument
|
|
|
|
|
* So I don't have to do some hacky switches/defines/magic strings
|
|
|
|
|
* eg:
|
|
|
|
|
* Plas_PP = get_partial_pressure(gas_mixture.plasma)
|
|
|
|
|
* O2_PP = get_partial_pressure(gas_mixture.oxygen)
|
|
|
|
|
* get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp
|
|
|
|
|
* get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
|
|
|
|
|
*
|
|
|
|
|
* 10/20*5 = 2.5
|
|
|
|
|
* 10 = 2.5/5*20
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure)
|
|
|
|
|
return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME
|
|
|
|
@@ -546,58 +554,168 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
|
|
|
|
|
/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure)
|
|
|
|
|
return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature)
|
|
|
|
|
|
|
|
|
|
///Mathematical proofs:
|
|
|
|
|
/**
|
|
|
|
|
get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp
|
|
|
|
|
get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
|
|
|
|
|
/**
|
|
|
|
|
* Counts how much pressure will there be if we impart MOLAR_ACCURACY amounts of our gas to the output gasmix.
|
|
|
|
|
* We do all of this without actually transferring it so dont worry about it changing the gasmix.
|
|
|
|
|
* Returns: Resulting pressure (number).
|
|
|
|
|
* Args:
|
|
|
|
|
* - output_air (gasmix).
|
|
|
|
|
*/
|
|
|
|
|
/datum/gas_mixture/proc/gas_pressure_minimum_transfer(datum/gas_mixture/output_air)
|
|
|
|
|
var/resulting_energy = output_air.thermal_energy() + (MOLAR_ACCURACY / total_moles() * thermal_energy())
|
|
|
|
|
var/resulting_capacity = output_air.heat_capacity() + (MOLAR_ACCURACY / total_moles() * heat_capacity())
|
|
|
|
|
return (output_air.total_moles() + MOLAR_ACCURACY) * R_IDEAL_GAS_EQUATION * (resulting_energy / resulting_capacity) / output_air.volume
|
|
|
|
|
|
|
|
|
|
10/20*5 = 2.5
|
|
|
|
|
10 = 2.5/5*20
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
/** Returns the amount of gas to be pumped to a specific container.
|
|
|
|
|
* Args:
|
|
|
|
|
* - output_air. The gas mix we want to pump to.
|
|
|
|
|
* - target_pressure. The target pressure we want.
|
|
|
|
|
* - ignore_temperature. Returns a cheaper form of gas calculation, useful if the temperature difference between the two gasmixes is low or nonexistant.
|
|
|
|
|
*/
|
|
|
|
|
/datum/gas_mixture/proc/gas_pressure_calculate(datum/gas_mixture/output_air, target_pressure, ignore_temperature = FALSE)
|
|
|
|
|
if((total_moles() <= 0) || (temperature <= 0))
|
|
|
|
|
return FALSE
|
|
|
|
|
|
|
|
|
|
var/pressure_delta = 0
|
|
|
|
|
if((output_air.temperature <= 0) || (output_air.total_moles() <= 0))
|
|
|
|
|
ignore_temperature = TRUE
|
|
|
|
|
pressure_delta = target_pressure
|
|
|
|
|
else
|
|
|
|
|
pressure_delta = target_pressure - output_air.return_pressure()
|
|
|
|
|
|
|
|
|
|
if(pressure_delta < 0.01 || gas_pressure_minimum_transfer(output_air) > target_pressure)
|
|
|
|
|
return FALSE
|
|
|
|
|
|
|
|
|
|
if(ignore_temperature)
|
|
|
|
|
return (pressure_delta*output_air.volume)/(temperature * R_IDEAL_GAS_EQUATION)
|
|
|
|
|
|
|
|
|
|
// Lower and upper bound for the moles we must transfer to reach the pressure. The answer is bound to be here somewhere.
|
|
|
|
|
var/pv = target_pressure * output_air.volume
|
|
|
|
|
var/rt_low = R_IDEAL_GAS_EQUATION * max(temperature, output_air.temperature) // Low refers to the resulting mole, this number is actually higher.
|
|
|
|
|
var/rt_high = R_IDEAL_GAS_EQUATION * min(temperature, output_air.temperature)
|
|
|
|
|
// These works by assuming our gas has extremely high heat capacity
|
|
|
|
|
// and the resultant gasmix will hit either the highest or lowest temperature possible.
|
|
|
|
|
var/lower_limit = max((pv / rt_low) - output_air.total_moles(), 0)
|
|
|
|
|
var/upper_limit = (pv / rt_high) - output_air.total_moles() // In theory this should never go below zero, the pressure_delta check above should account for this.
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We have PV=nRT as a nice formula, we can rearrange it into nT = PV/R
|
|
|
|
|
* But now both n and T can change, since any incoming moles also change our temperature.
|
|
|
|
|
* So we need to unify both our n and T, somehow.
|
|
|
|
|
*
|
|
|
|
|
* We can rewrite T as (our old thermal energy + incoming thermal energy) divided by (our old heat capacity + incoming heat capacity)
|
|
|
|
|
* T = (W1 + n/N2 * W2) / (C1 + n/N2 * C2). C being heat capacity, W being work, N being total moles.
|
|
|
|
|
*
|
|
|
|
|
* In total we now have our equation be: (N1 + n) * (W1 + n/N2 * W2) / (C1 + n/N2 * C2) = PV/R
|
|
|
|
|
* Now you can rearrange this and find out that it's a quadratic equation and pretty much solvable with the formula. Will be a bit messy though.
|
|
|
|
|
*
|
|
|
|
|
* W2/N2n^2 +
|
|
|
|
|
* (N1*W2/N2)n + W1n - ((PV/R)*C2/N2)n +
|
|
|
|
|
* (-(PV/R)*C1) + N1W1 = 0
|
|
|
|
|
*
|
|
|
|
|
* We will represent each of these terms with A, B, and C. A for the n^2 part, B for the n^1 part, and C for the n^0 part.
|
|
|
|
|
* We then put this into the famous (-b +/- sqrt(b^2-4ac)) / 2a formula.
|
|
|
|
|
*
|
|
|
|
|
* Oh, and one more thing. By "our" we mean the gasmix in the argument. We are the incoming one here. We are number 2, target is number 1.
|
|
|
|
|
* If all this counting fucks up, we revert first to Newton's approximation, then the old simple formula.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Our thermal energy and moles
|
|
|
|
|
var/w2 = thermal_energy()
|
|
|
|
|
var/n2 = total_moles()
|
|
|
|
|
var/c2 = heat_capacity()
|
|
|
|
|
|
|
|
|
|
// Target thermal energy and moles
|
|
|
|
|
var/w1 = output_air.thermal_energy()
|
|
|
|
|
var/n1 = output_air.total_moles()
|
|
|
|
|
var/c1 = output_air.heat_capacity()
|
|
|
|
|
|
|
|
|
|
/// The PV/R part in our equation.
|
|
|
|
|
var/pvr = pv / R_IDEAL_GAS_EQUATION
|
|
|
|
|
|
|
|
|
|
/// x^2 in the quadratic
|
|
|
|
|
var/a_value = w2/n2
|
|
|
|
|
/// x^1 in the quadratic
|
|
|
|
|
var/b_value = ((n1*w2)/n2) + w1 - (pvr*c2/n2)
|
|
|
|
|
/// x^0 in the quadratic
|
|
|
|
|
var/c_value = (-1*pvr*c1) + n1 * w1
|
|
|
|
|
|
|
|
|
|
. = gas_pressure_quadratic(a_value, b_value, c_value, lower_limit, upper_limit)
|
|
|
|
|
if(.)
|
|
|
|
|
return
|
|
|
|
|
. = gas_pressure_approximate(a_value, b_value, c_value, lower_limit, upper_limit)
|
|
|
|
|
if(.)
|
|
|
|
|
return
|
|
|
|
|
// Inaccurate and will probably explode but whatever.
|
|
|
|
|
return (pressure_delta*output_air.volume)/(temperature * R_IDEAL_GAS_EQUATION)
|
|
|
|
|
|
|
|
|
|
/// Actually tries to solve the quadratic equation.
|
|
|
|
|
/// Do mind that the numbers can get very big and might hit BYOND's single point float limit.
|
|
|
|
|
/datum/gas_mixture/proc/gas_pressure_quadratic(a, b, c, lower_limit, upper_limit)
|
|
|
|
|
var/solution
|
|
|
|
|
if(!IS_INF_OR_NAN(a) && !IS_INF_OR_NAN(b) && !IS_INF_OR_NAN(c))
|
|
|
|
|
solution = max(SolveQuadratic(a, b, c))
|
|
|
|
|
if((solution > lower_limit) && (solution < upper_limit)) //SolveQuadratic can return nulls so be careful here
|
|
|
|
|
return solution
|
|
|
|
|
stack_trace("Failed to solve pressure quadratic equation. A: [a]. B: [b]. C:[c]. Current value = [solution]. Expected lower limit: [lower_limit]. Expected upper limit: [upper_limit].")
|
|
|
|
|
return FALSE
|
|
|
|
|
|
|
|
|
|
/// Approximation of the quadratic equation using Newton-Raphson's Method.
|
|
|
|
|
/// We use the slope of an approximate value to get closer to the root of a given equation.
|
|
|
|
|
/datum/gas_mixture/proc/gas_pressure_approximate(a, b, c, lower_limit, upper_limit)
|
|
|
|
|
var/solution
|
|
|
|
|
if(!IS_INF_OR_NAN(a) && !IS_INF_OR_NAN(b) && !IS_INF_OR_NAN(c))
|
|
|
|
|
/// We need to start off at a reasonably good estimate. For very big numbers the amount of moles is most likely small so better start with lower_limit.
|
|
|
|
|
solution = lower_limit
|
|
|
|
|
for (var/iteration in 1 to ATMOS_PRESSURE_APPROXIMATION_ITERATIONS)
|
|
|
|
|
var/diff = (a*solution**2 + b*solution + c) / (2*a*solution + b) // f(sol) / f'(sol)
|
|
|
|
|
solution -= diff // xn+1 = xn - f(sol) / f'(sol)
|
|
|
|
|
if(abs(diff) < MOLAR_ACCURACY && (solution > lower_limit) && (solution < upper_limit))
|
|
|
|
|
return solution
|
|
|
|
|
stack_trace("Newton's Approximation for pressure failed after [ATMOS_PRESSURE_APPROXIMATION_ITERATIONS] iterations. A: [a]. B: [b]. C:[c]. Current value: [solution]. Expected lower limit: [lower_limit]. Expected upper limit: [upper_limit].")
|
|
|
|
|
return FALSE
|
|
|
|
|
|
|
|
|
|
/// Pumps gas from src to output_air. Amount depends on target_pressure
|
|
|
|
|
/datum/gas_mixture/proc/pump_gas_to(datum/gas_mixture/output_air, target_pressure, specific_gas = null)
|
|
|
|
|
var/output_starting_pressure = output_air.return_pressure()
|
|
|
|
|
var/temperature_delta = abs(temperature - output_air.temperature)
|
|
|
|
|
var/datum/gas_mixture/removed
|
|
|
|
|
var/transfer_moles
|
|
|
|
|
|
|
|
|
|
if((target_pressure - output_starting_pressure) < 0.01)
|
|
|
|
|
//No need to pump gas if target is already reached!
|
|
|
|
|
if(specific_gas)
|
|
|
|
|
// This is necessary because the specific heat capacity of a gas might be different from our gasmix.
|
|
|
|
|
var/datum/gas_mixture/temporary = remove_specific_ratio(specific_gas, 1)
|
|
|
|
|
transfer_moles = temporary.gas_pressure_calculate(output_air, target_pressure, temperature_delta <= 5)
|
|
|
|
|
removed = temporary.remove_specific(specific_gas, transfer_moles)
|
|
|
|
|
else
|
|
|
|
|
transfer_moles = gas_pressure_calculate(output_air, target_pressure, temperature_delta <= 5)
|
|
|
|
|
removed = remove(transfer_moles)
|
|
|
|
|
|
|
|
|
|
if(!removed)
|
|
|
|
|
return FALSE
|
|
|
|
|
|
|
|
|
|
//Calculate necessary moles to transfer using PV=nRT
|
|
|
|
|
if((total_moles() > 0) && (temperature>0))
|
|
|
|
|
var/pressure_delta = target_pressure - output_starting_pressure
|
|
|
|
|
var/transfer_moles = (pressure_delta*output_air.volume)/(temperature * R_IDEAL_GAS_EQUATION)
|
|
|
|
|
|
|
|
|
|
//Actually transfer the gas
|
|
|
|
|
if(specific_gas)
|
|
|
|
|
var/datum/gas_mixture/removed = remove_specific(specific_gas, transfer_moles)
|
|
|
|
|
output_air.merge(removed)
|
|
|
|
|
return TRUE
|
|
|
|
|
var/datum/gas_mixture/removed = remove(transfer_moles)
|
|
|
|
|
output_air.merge(removed)
|
|
|
|
|
return TRUE
|
|
|
|
|
return FALSE
|
|
|
|
|
output_air.merge(removed)
|
|
|
|
|
return TRUE
|
|
|
|
|
|
|
|
|
|
/// Releases gas from src to output air. This means that it can not transfer air to gas mixture with higher pressure.
|
|
|
|
|
/datum/gas_mixture/proc/release_gas_to(datum/gas_mixture/output_air, target_pressure, rate=1)
|
|
|
|
|
var/output_starting_pressure = output_air.return_pressure()
|
|
|
|
|
var/input_starting_pressure = return_pressure()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Need at least 10 KPa difference to overcome friction in the mechanism
|
|
|
|
|
if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10))
|
|
|
|
|
//No need to pump gas if target is already reached or input pressure is too low
|
|
|
|
|
//Need at least 10 KPa difference to overcome friction in the mechanism
|
|
|
|
|
return FALSE
|
|
|
|
|
//Can not have a pressure delta that would cause output_pressure > input_pressure
|
|
|
|
|
target_pressure = output_starting_pressure + min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2)
|
|
|
|
|
var/temperature_delta = abs(temperature - output_air.temperature)
|
|
|
|
|
|
|
|
|
|
var/transfer_moles = gas_pressure_calculate(output_air, target_pressure, temperature_delta <= 5)
|
|
|
|
|
|
|
|
|
|
//Actually transfer the gas
|
|
|
|
|
var/datum/gas_mixture/removed = remove(transfer_moles * rate)
|
|
|
|
|
|
|
|
|
|
if(!removed)
|
|
|
|
|
return FALSE
|
|
|
|
|
|
|
|
|
|
//Calculate necessary moles to transfer using PV = nRT
|
|
|
|
|
if((total_moles() > 0) && (temperature>0))
|
|
|
|
|
var/pressure_delta = min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2)
|
|
|
|
|
//Can not have a pressure delta that would cause output_pressure > input_pressure
|
|
|
|
|
|
|
|
|
|
var/transfer_moles = (pressure_delta*output_air.volume)/(temperature * R_IDEAL_GAS_EQUATION)
|
|
|
|
|
|
|
|
|
|
//Actually transfer the gas
|
|
|
|
|
var/datum/gas_mixture/removed = remove(transfer_moles * rate)
|
|
|
|
|
output_air.merge(removed)
|
|
|
|
|
|
|
|
|
|
return TRUE
|
|
|
|
|
return FALSE
|
|
|
|
|
output_air.merge(removed)
|
|
|
|
|
return TRUE
|
|
|
|
|