mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-06-09 10:06:46 +02:00
@@ -9,7 +9,7 @@ namespace Content.Shared.Temperature.HeatContainer;
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
[Access(typeof(HeatContainerHelpers), typeof(SharedAtmosphereSystem))]
|
||||
public partial struct HeatContainer : IRobustCloneable<HeatContainer>, IHeatContainer
|
||||
public partial struct HeatContainer : IHeatContainer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField]
|
||||
@@ -29,22 +29,15 @@ public partial struct HeatContainer : IRobustCloneable<HeatContainer>, IHeatCont
|
||||
|
||||
public HeatContainer(float heatCapacity, float temperature)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(heatCapacity);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(temperature);
|
||||
HeatCapacity = heatCapacity;
|
||||
Temperature = temperature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy constructor for implementing ICloneable.
|
||||
/// </summary>
|
||||
/// <param name="c">The HeatContainer to copy.</param>
|
||||
private HeatContainer(HeatContainer c)
|
||||
public HeatContainer(float heatCapacity)
|
||||
{
|
||||
HeatCapacity = c.HeatCapacity;
|
||||
Temperature = c.Temperature;
|
||||
}
|
||||
|
||||
public HeatContainer Clone()
|
||||
{
|
||||
return new HeatContainer(this);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(heatCapacity);
|
||||
HeatCapacity = heatCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Temperature.HeatContainer;
|
||||
|
||||
public static partial class HeatContainerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Splits a <see cref="IHeatContainer"/> into two.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="IHeatContainer"/> to split. This will be modified to contain the remaining heat capacity.</param>
|
||||
/// <param name="cSplit">A <see cref="IHeatContainer"/> that will be modified to contain
|
||||
/// the specified fraction of the original container's heat capacity and the same temperature.</param>
|
||||
/// <param name="fraction">The fraction of the heat capacity to move to the new container. Clamped between 0 and 1.</param>
|
||||
[PublicAPI]
|
||||
public static void Split<T1, T2>(ref T1 c, ref T2 cSplit, float fraction = 0.5f)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : IHeatContainer
|
||||
{
|
||||
fraction = Math.Clamp(fraction, 0f, 1f);
|
||||
var newHeatCapacity = c.HeatCapacity * fraction;
|
||||
|
||||
cSplit.HeatCapacity = newHeatCapacity;
|
||||
cSplit.Temperature = c.Temperature;
|
||||
|
||||
c.HeatCapacity -= newHeatCapacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a <see cref="IHeatContainer"/> into two,
|
||||
/// modifying the original container to contain the specified fraction of the original heat capacity and the same temperature.
|
||||
/// </summary>
|
||||
/// <param name="c">A <see cref="IHeatContainer"/> that will be modified to contain
|
||||
/// the specified fraction of the original container's heat capacity and the same temperature.</param>
|
||||
/// <param name="fraction">The fraction of the heat capacity to move to the new container. Clamped between 0 and 1.</param>
|
||||
/// <remarks>This discards the leftover fraction. Be very careful with using this as you may void heat unintentionally.</remarks>
|
||||
[PublicAPI]
|
||||
public static void Split<T>(ref T c, float fraction = 0.5f)
|
||||
where T : IHeatContainer
|
||||
{
|
||||
fraction = Math.Clamp(fraction, 0f, 1f);
|
||||
var newHeatCapacity = c.HeatCapacity * fraction;
|
||||
c.HeatCapacity = newHeatCapacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides a source <see cref="IHeatContainer"/> into a specified number of equal parts.
|
||||
/// </summary>
|
||||
/// <param name="c">The input <see cref="IHeatContainer"/> to split.</param>
|
||||
/// <param name="dividedArray">An array of <see cref="IHeatContainer"/>s equally split from the source <see cref="IHeatContainer"/>.
|
||||
/// This will be written to. This must be the same length as num.</param>
|
||||
/// <param name="num">The number of <see cref="IHeatContainer"/>s
|
||||
/// to split the source <see cref="IHeatContainer"/> into.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when attempting to divide the source container by zero.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of the divided array does not match the specified number of divisions.</exception>
|
||||
[PublicAPI]
|
||||
public static void Divide<T>(this T c, T[] dividedArray, int num)
|
||||
where T : struct, IHeatContainer // if we allowed classes you'd just have an array reffing the same obj
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(num);
|
||||
ArgumentOutOfRangeException.ThrowIfNotEqual(dividedArray.Length, num);
|
||||
|
||||
var fraction = 1f / num;
|
||||
Split(ref c, fraction);
|
||||
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
dividedArray[i] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,11 @@ public static partial class HeatContainerHelpers
|
||||
/// <returns>The amount of transferred heat in joules that is needed
|
||||
/// to bring the containers to thermal equilibrium.</returns>
|
||||
/// <example>A positive value indicates heat transfer from a hot cA to a cold cB.</example>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the combined heat capacity of both containers is zero or negative.</exception>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumHeatQuery<T1, T2>(ref T1 cA, ref T2 cB)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : IHeatContainer
|
||||
{
|
||||
var cTotal = cA.HeatCapacity + cB.HeatCapacity;
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(cTotal);
|
||||
|
||||
/*
|
||||
The solution is derived from the following facts:
|
||||
1. Let Q be the amount of heat energy transferred from cA to cB.
|
||||
@@ -37,6 +33,7 @@ public static partial class HeatContainerHelpers
|
||||
6. At thermal equilibrium, T_A_final = T_B_final.
|
||||
7. Solve for Q.
|
||||
*/
|
||||
var cTotal = cA.HeatCapacity + cB.HeatCapacity;
|
||||
return (cA.Temperature - cB.Temperature) *
|
||||
(cA.HeatCapacity * cB.HeatCapacity / cTotal);
|
||||
}
|
||||
@@ -48,14 +45,12 @@ public static partial class HeatContainerHelpers
|
||||
/// <param name="cA">The first <see cref="IHeatContainer"/> to exchange heat.</param>
|
||||
/// <param name="cB">The second <see cref="IHeatContainer"/> to exchange heat with.</param>
|
||||
/// <returns>The resulting equilibrium temperature both containers will be at.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the combined heat capacity of both containers is zero or negative.</exception>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumTemperatureQuery<T1, T2>(ref T1 cA, ref T2 cB)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : IHeatContainer
|
||||
{
|
||||
var cTotal = cA.HeatCapacity + cB.HeatCapacity;
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(cTotal);
|
||||
// Insert the above solution for Q into T_A_final = T_A_initial - Q / C_A and rearrange the result.
|
||||
return (cA.HeatCapacity * cA.Temperature + cB.HeatCapacity * cB.Temperature) / cTotal;
|
||||
}
|
||||
@@ -91,7 +86,7 @@ public static partial class HeatContainerHelpers
|
||||
cA.Temperature = tFinal;
|
||||
cB.Temperature = tFinal;
|
||||
// Guarded against div/0 in EquilibriumTemperatureQuery: totalHeatCapacity > 0.
|
||||
dQ = (tInitialA - tFinal) / cA.HeatCapacity;
|
||||
dQ = (tInitialA - tFinal) * cA.HeatCapacity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -103,7 +98,7 @@ public static partial class HeatContainerHelpers
|
||||
/// </summary>
|
||||
/// <param name="cN">The array of <see cref="IHeatContainer"/>s to bring into thermal equilibrium.</param>
|
||||
[PublicAPI]
|
||||
public static void Equilibrate<T>(this T[] cN) where T : IHeatContainer
|
||||
public static void Equilibrate<T>(T[] cN) where T : IHeatContainer
|
||||
{
|
||||
var tF = EquilibriumTemperatureQuery(cN);
|
||||
for (var i = 0; i < cN.Length; i++)
|
||||
@@ -140,7 +135,7 @@ public static partial class HeatContainerHelpers
|
||||
/// <returns>The temperature of all <see cref="IHeatContainer"/>s involved after reaching thermal equilibrium.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the combined heat capacity of all containers is zero or negative.</exception>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumTemperatureQuery<T>(this T[] cN) where T : IHeatContainer
|
||||
public static float EquilibriumTemperatureQuery<T>(T[] cN) where T : IHeatContainer
|
||||
{
|
||||
/*
|
||||
The solution is derived via the following:
|
||||
@@ -194,7 +189,7 @@ public static partial class HeatContainerHelpers
|
||||
/// to reach thermal equilibrium.</param>
|
||||
/// <returns>The temperature of all <see cref="IHeatContainer"/>s involved after reaching thermal equilibrium.</returns>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumTemperatureQuery<T>(this T[] cN, out float[] dQ) where T : IHeatContainer
|
||||
public static float EquilibriumTemperatureQuery<T>(T[] cN, out float[] dQ) where T : IHeatContainer
|
||||
{
|
||||
/*
|
||||
For finding the total heat exchanged during the equalization between a group of bodies
|
||||
@@ -203,7 +198,7 @@ public static partial class HeatContainerHelpers
|
||||
dQ = C * (T_f - T_i) for each container
|
||||
*/
|
||||
|
||||
var tF = cN.EquilibriumTemperatureQuery();
|
||||
var tF = EquilibriumTemperatureQuery(cN);
|
||||
dQ = new float[cN.Length];
|
||||
|
||||
for (var i = 0; i < cN.Length; i++)
|
||||
@@ -230,7 +225,7 @@ public static partial class HeatContainerHelpers
|
||||
cAll[0] = cA;
|
||||
cN.CopyTo(cAll, 1);
|
||||
|
||||
return cAll.EquilibriumTemperatureQuery();
|
||||
return EquilibriumTemperatureQuery(cAll);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -5,64 +5,65 @@ namespace Content.Shared.Temperature.HeatContainer;
|
||||
public static partial class HeatContainerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Merges two heat containers into one, conserving total internal energy.
|
||||
/// Merges one heat container into another.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="IHeatContainer"/> to merge. This will be modified to contain the merged result.</param>
|
||||
/// <param name="cB">The second <see cref="IHeatContainer"/> to merge.</param>
|
||||
/// <param name="cB">The second <see cref="IHeatContainer"/> to merge. This will remain unmodified.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the combined heat capacity of both containers is zero or negative.</exception>
|
||||
[PublicAPI]
|
||||
public static void Merge<T1, T2>(ref T1 cA, ref T2 cB)
|
||||
public static void MergeInto<T1, T2>(ref T1 cA, ref T2 cB)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : IHeatContainer
|
||||
{
|
||||
var combinedHeatCapacity = cA.HeatCapacity + cB.HeatCapacity;
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(combinedHeatCapacity);
|
||||
|
||||
cA.HeatCapacity = combinedHeatCapacity;
|
||||
cA.Temperature = (cA.InternalEnergy + cB.InternalEnergy) / combinedHeatCapacity;
|
||||
cA.HeatCapacity = combinedHeatCapacity;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Merges an array of <see cref="IHeatContainer"/>s into a single heat container, conserving total internal energy.
|
||||
/// Merges an array of <see cref="IHeatContainer"/>s into a single heat container.
|
||||
/// This means you combine N+1 containers into 1.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="IHeatContainer"/> to merge.
|
||||
/// This will be modified to contain the merged result.</param>
|
||||
/// <param name="cN">The array of <see cref="IHeatContainer"/>s to merge.</param>
|
||||
/// <param name="cA">The first <see cref="IHeatContainer"/> to merge. This will be modified to contain the merged result.</param>
|
||||
/// <param name="cN">The array of <see cref="IHeatContainer"/>s to merge. These will remain unmodified.</param>
|
||||
[PublicAPI]
|
||||
public static void Merge<T1, T2>(ref T1 cA, T2[] cN)
|
||||
public static void MergeInto<T1, T2>(ref T1 cA, T2[] cN)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : IHeatContainer
|
||||
{
|
||||
// merge the first array and then merge the result with cA to avoid alloc
|
||||
var temp = new HeatContainer();
|
||||
cN.Merge(ref temp);
|
||||
Merge(ref cA, ref temp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges an array of <see cref="IHeatContainer"/>s into a single heat container, conserving total internal energy.
|
||||
/// </summary>
|
||||
/// <param name="cN">The array of <see cref="IHeatContainer"/>s to merge.</param>
|
||||
/// <param name="result">The modified <see cref="IHeatContainer"/> containing the merged result.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the combined heat capacity of all containers is zero or negative.</exception>
|
||||
[PublicAPI]
|
||||
public static void Merge<T1, T2>(this T1[] cN, ref T2 result)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : IHeatContainer
|
||||
{
|
||||
var totalHeatCapacity = 0f;
|
||||
var totalEnergy = 0f;
|
||||
|
||||
foreach (var c in cN)
|
||||
var totalEnergy = cA.InternalEnergy;
|
||||
var totalHeatCapacity = cA.HeatCapacity;
|
||||
for (var i = 0; i < cN.Length; i++)
|
||||
{
|
||||
totalHeatCapacity += c.HeatCapacity;
|
||||
totalEnergy += c.InternalEnergy;
|
||||
totalEnergy += cN[i].InternalEnergy;
|
||||
totalHeatCapacity += cN[i].HeatCapacity;
|
||||
}
|
||||
cA.Temperature = totalEnergy / totalHeatCapacity;
|
||||
cA.HeatCapacity = totalHeatCapacity;
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(totalHeatCapacity);
|
||||
|
||||
result.HeatCapacity = totalHeatCapacity;
|
||||
result.Temperature = totalEnergy / totalHeatCapacity;
|
||||
/// <summary>
|
||||
/// Merges an array of <see cref="IHeatContainer"/>s into a single new output heat container.
|
||||
/// This means you combine N containers into 1.
|
||||
/// </summary>
|
||||
/// <param name="cA">The <see cref="IHeatContainer"/> to write the result to.</param>
|
||||
/// <param name="cN">The array of <see cref="IHeatContainer"/>s to merge. These will remain unmodified.</param>
|
||||
[PublicAPI]
|
||||
public static void MergeAndCopy<T1, T2>(ref T1 cA, T2[] cN)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : IHeatContainer
|
||||
{
|
||||
var totalEnergy = 0f;
|
||||
var totalHeatCapacity = 0f;
|
||||
for (var i = 0; i < cN.Length; i++)
|
||||
{
|
||||
totalEnergy += cN[i].InternalEnergy;
|
||||
totalHeatCapacity += cN[i].HeatCapacity;
|
||||
}
|
||||
cA.Temperature = totalEnergy / totalHeatCapacity;
|
||||
cA.HeatCapacity = totalHeatCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Temperature.HeatContainer;
|
||||
|
||||
public static partial class HeatContainerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Splits a <see cref="IHeatContainer"/> into two, modifying the original container
|
||||
/// to contain the remaining fraction of the original heat capacity and the same temperature.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="IHeatContainer"/> to split. This will be modified to contain the remaining heat capacity.</param>
|
||||
/// <param name="cSplit">A <see cref="IHeatContainer"/> that will be modified to contain
|
||||
/// the specified fraction of the original container's heat capacity and the same temperature. Any previous value will be overwritten.</param>
|
||||
/// <param name="fraction">The fraction of the heat capacity to move to the new container. Clamped between 0 and 1.</param>
|
||||
[PublicAPI]
|
||||
public static void SplitFrom<T1, T2>(ref T1 c, ref T2 cSplit, float fraction = 0.5f)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : IHeatContainer
|
||||
{
|
||||
fraction = Math.Clamp(fraction, 0f, 1f);
|
||||
var newHeatCapacity = c.HeatCapacity * fraction;
|
||||
|
||||
cSplit.HeatCapacity = newHeatCapacity;
|
||||
cSplit.Temperature = c.Temperature;
|
||||
|
||||
c.HeatCapacity -= newHeatCapacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a <see cref="IHeatContainer"/> into two, modifying the original container
|
||||
/// to contain the remaining fraction of the original heat capacity and the same temperature,
|
||||
/// while discarding the rest.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="IHeatContainer"/> to split off. This will be modified to contain the remaining heat capacity.</param>
|
||||
/// <param name="fraction">The fraction of the heat capacity to remove from the original container. Clamped between 0 and 1.</param>
|
||||
/// <remarks>This discards the leftover fraction. Be very careful with using this as you may void heat unintentionally.</remarks>
|
||||
[PublicAPI]
|
||||
public static void SplitFrom<T>(ref T c, float fraction = 0.5f)
|
||||
where T : IHeatContainer
|
||||
{
|
||||
fraction = Math.Clamp(fraction, 0f, 1f);
|
||||
var newHeatCapacity = c.HeatCapacity * fraction;
|
||||
|
||||
c.HeatCapacity -= newHeatCapacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a source <see cref="IHeatContainer"/> into a specified number of equal parts.
|
||||
/// This means you will get N + 1 equal parts where N is the length of the given array.
|
||||
/// </summary>
|
||||
/// <param name="c">The input <see cref="IHeatContainer"/> to split. It will be modified such that it is equal to each entry in <paramref name="dividedArray"/>.</param>
|
||||
/// <param name="dividedArray">An array of <see cref="IHeatContainer"/>s equally split from the source.<see cref="IHeatContainer"/>.
|
||||
/// This will be written to.</param>
|
||||
[PublicAPI]
|
||||
public static void SplitFrom<T1, T2>(ref T1 c, T2[] dividedArray)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : struct, IHeatContainer // if we allowed classes you'd just have an array reffing the same obj
|
||||
{
|
||||
var num = dividedArray.Length + 1;
|
||||
for (var i = 0; i < dividedArray.Length; i++)
|
||||
{
|
||||
dividedArray[i].Temperature = c.Temperature;
|
||||
dividedArray[i].HeatCapacity = c.HeatCapacity / num;
|
||||
}
|
||||
|
||||
c.HeatCapacity /= num;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a source <see cref="IHeatContainer"/> into a specified number of equal parts, keeping the source unmodified.
|
||||
/// This means you will get N equal parts where N is the length of the given array.
|
||||
/// </summary>
|
||||
/// <param name="c">The input <see cref="IHeatContainer"/> to split into equal parts. This container is not modified, so make sure to discard it to avoid breaking energy conservation.</param>
|
||||
/// <param name="dividedArray">An array of <see cref="IHeatContainer"/>s the source will be split into.
|
||||
/// This will be written to.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when attempting to divide the source container by zero.</exception>
|
||||
[PublicAPI]
|
||||
public static void SplitAndCopy<T1, T2>(ref T1 c, T2[] dividedArray)
|
||||
where T1 : IHeatContainer
|
||||
where T2 : struct, IHeatContainer // if we allowed classes you'd just have an array reffing the same obj
|
||||
{
|
||||
var num = dividedArray.Length;
|
||||
ArgumentOutOfRangeException.ThrowIfZero(num);
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
dividedArray[i].Temperature = c.Temperature;
|
||||
dividedArray[i].HeatCapacity = c.HeatCapacity / num;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ public static partial class HeatContainerHelpers
|
||||
/// Positive values add heat, negative values remove heat.
|
||||
/// The temperature can never become lower than 0K even if more heat is removed.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="IHeatContainer"/> to add or remove energy.</param>
|
||||
/// <param name="dQ">The energy in joules to add or remove.</param>
|
||||
/// <param name="c">The <see cref="IHeatContainer"/> to add heat to or remove heat from.</param>
|
||||
/// <param name="dQ">The amount of energy in joules to add or remove.</param>
|
||||
[PublicAPI]
|
||||
public static void AddHeat<T>(ref T c, float dQ) where T : IHeatContainer
|
||||
{
|
||||
@@ -27,13 +27,11 @@ public static partial class HeatContainerHelpers
|
||||
/// The temperature can never become lower than 0K even if more heat is removed.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="IHeatContainer"/> to query.</param>
|
||||
/// <param name="dQ">The energy in joules to add or remove.</param>
|
||||
/// <param name="dQ">The amount of energy in joules to add or remove.</param>
|
||||
/// <returns>The resulting temperature in kelvin after the heat change.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the heat capacity of the container is zero or negative.</exception>
|
||||
[PublicAPI]
|
||||
public static float AddHeatQuery<T>(ref T c, float dQ) where T : IHeatContainer
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(c.HeatCapacity);
|
||||
// Don't allow the temperature to go below the absolute minimum.
|
||||
return Math.Max(0f, c.Temperature + dQ / c.HeatCapacity);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,612 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Temperature.HeatContainer;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Content.Tests.Shared.Temperature;
|
||||
|
||||
[TestFixture, TestOf(typeof(HeatContainer))]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public sealed class HeatContainerTest
|
||||
{
|
||||
#region HeatContainerHelpers
|
||||
[Test]
|
||||
public void AddHeatTest()
|
||||
{
|
||||
// T = 100 K
|
||||
// C = 1000 J/K
|
||||
var c = new HeatContainer(1000f, 100f);
|
||||
var originalEnergy = c.InternalEnergy;
|
||||
|
||||
// Check initial values.
|
||||
Assert.That(c.Temperature, Is.EqualTo(100f));
|
||||
Assert.That(c.HeatCapacity, Is.EqualTo(1000f));
|
||||
|
||||
// Add 5000 J, the temperature should rise by 5 K.
|
||||
HeatContainerHelpers.AddHeat(ref c, 5000);
|
||||
Assert.That(c.Temperature, Is.EqualTo(105f).Within(1).Ulps);
|
||||
Assert.That(c.HeatCapacity, Is.EqualTo(1000f));
|
||||
Assert.That(c.InternalEnergy, Is.EqualTo(originalEnergy + 5000).Within(1).Ulps);
|
||||
|
||||
// Subtract 15000 J, the temperature should lower by 15 K.
|
||||
HeatContainerHelpers.AddHeat(ref c, -15000);
|
||||
Assert.That(c.Temperature, Is.EqualTo(90f).Within(1).Ulps);
|
||||
Assert.That(c.HeatCapacity, Is.EqualTo(1000f));
|
||||
Assert.That(c.InternalEnergy, Is.EqualTo(originalEnergy + 5000 - 15000).Within(1).Ulps);
|
||||
|
||||
// Check that we cannot go below 0 K.
|
||||
HeatContainerHelpers.AddHeat(ref c, -200000f);
|
||||
Assert.That(c.Temperature, Is.Zero);
|
||||
Assert.That(c.HeatCapacity, Is.EqualTo(1000f));
|
||||
Assert.That(c.InternalEnergy, Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddHeatQueryTest()
|
||||
{
|
||||
// T = 100 K
|
||||
// C = 1000 J/K
|
||||
var c = new HeatContainer(1000f, 100f);
|
||||
|
||||
// Check initial values.
|
||||
Assert.That(c.Temperature, Is.EqualTo(100f));
|
||||
Assert.That(c.HeatCapacity, Is.EqualTo(1000f));
|
||||
|
||||
// Add 5000 J, the temperature should rise by 5 K from the original value.
|
||||
Assert.That(HeatContainerHelpers.AddHeatQuery(ref c, 5000), Is.EqualTo(105f).Within(1).Ulps);
|
||||
|
||||
// Subtract 15000 J, the temperature should lower by 15 K from the original value.
|
||||
Assert.That(HeatContainerHelpers.AddHeatQuery(ref c, -15000), Is.EqualTo(85f).Within(1).Ulps);
|
||||
|
||||
// Check that we cannot go below 0 K.
|
||||
Assert.That(HeatContainerHelpers.AddHeatQuery(ref c, -200000f), Is.Zero);
|
||||
|
||||
// The original container should be unchanged.
|
||||
Assert.That(c.Temperature, Is.EqualTo(100f));
|
||||
Assert.That(c.HeatCapacity, Is.EqualTo(1000f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHeatCapacityTest()
|
||||
{
|
||||
// T = 300 K
|
||||
// C = 1000 J/K
|
||||
var c = new HeatContainer(1000f, 300f);
|
||||
var originalEnergy = c.InternalEnergy;
|
||||
|
||||
// We triple the heat capacity, resulting in the temeperature to become one third of the original.
|
||||
HeatContainerHelpers.SetHeatCapacity(ref c, 3000f);
|
||||
|
||||
// The original container should be unchanged.
|
||||
Assert.That(c.Temperature, Is.EqualTo(100f).Within(1).Ulps);
|
||||
Assert.That(c.HeatCapacity, Is.EqualTo(3000f));
|
||||
|
||||
// The total energy is conserved.
|
||||
Assert.That(c.InternalEnergy, Is.EqualTo(originalEnergy).Within(1).Ulps);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Divide
|
||||
[Test]
|
||||
public void SplitTest()
|
||||
{
|
||||
// T = 42 K
|
||||
// C = 3000 J/K
|
||||
var c1 = new HeatContainer(3000f, 42f);
|
||||
var c2 = new HeatContainer();
|
||||
var totalEnergy = c1.InternalEnergy;
|
||||
|
||||
// Split equally.
|
||||
HeatContainerHelpers.SplitFrom(ref c1, ref c2, fraction: 0.5f);
|
||||
|
||||
// The heat capacity should be split equally.
|
||||
// The temperature should be the same.
|
||||
// The total energy should be conserved.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(42f));
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(1500f).Within(1).Ulps);
|
||||
Assert.That(c2.Temperature, Is.EqualTo(42f));
|
||||
Assert.That(c2.HeatCapacity, Is.EqualTo(1500f).Within(1).Ulps);
|
||||
Assert.That(c1.InternalEnergy + c2.InternalEnergy, Is.EqualTo(totalEnergy).Within(1).Ulps);
|
||||
|
||||
// Reset the first container.
|
||||
c1 = new HeatContainer(3000f, 42f);
|
||||
|
||||
// Split into 2/3 + 1/3.
|
||||
HeatContainerHelpers.SplitFrom(ref c1, ref c2, fraction: 1f / 3);
|
||||
|
||||
// The heat capacity should be split according to the fraction.
|
||||
// The temperature should be the same.
|
||||
// The total energy should be conserved.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(42f));
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(2000f).Within(1).Ulps);
|
||||
Assert.That(c2.Temperature, Is.EqualTo(42f));
|
||||
Assert.That(c2.HeatCapacity, Is.EqualTo(1000f).Within(1).Ulps);
|
||||
Assert.That(c1.InternalEnergy + c2.InternalEnergy, Is.EqualTo(totalEnergy).Within(1).Ulps);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SplitDiscardTest()
|
||||
{
|
||||
// T = 42 K
|
||||
// C = 3000 J/K
|
||||
var c1 = new HeatContainer(3000f, 42f);
|
||||
|
||||
// Split equally.
|
||||
HeatContainerHelpers.SplitFrom(ref c1, fraction: 0.5f);
|
||||
|
||||
// The heat capacity should be split equally, the temperature should be the same.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(42f));
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(1500f).Within(1).Ulps);
|
||||
|
||||
// Reset the container.
|
||||
c1 = new HeatContainer(3000f, 42f);
|
||||
|
||||
// Split into 1/3 + 2/3.
|
||||
HeatContainerHelpers.SplitFrom(ref c1, fraction: 1f / 3);
|
||||
|
||||
// The heat capacity should be split according to the fraction, the temperature should be the same.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(42f));
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(2000f).Within(1).Ulps);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SplitArrayTest()
|
||||
{
|
||||
// T = 42 K
|
||||
// C = 1000 J/K
|
||||
const int n = 4;
|
||||
var c1 = new HeatContainer(1000f, 42f);
|
||||
var cA = new HeatContainer[n];
|
||||
|
||||
// Split into n + 1 equal parts.
|
||||
HeatContainerHelpers.SplitFrom(ref c1, cA);
|
||||
|
||||
for (var i = 0; i < n; i++)
|
||||
{
|
||||
// The temperature should be the same as the initial one.
|
||||
// The heat capacities should be equally split.
|
||||
Assert.That(cA[i].Temperature, Is.EqualTo(42f));
|
||||
Assert.That(cA[i].HeatCapacity, Is.EqualTo(1000f / (n + 1)).Within(1).Ulps);
|
||||
}
|
||||
|
||||
// Check that the initital container is the same as the output containers.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(42f));
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(1000f / (n + 1)).Within(1).Ulps);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SplitAndCopyTest()
|
||||
{
|
||||
// T = 42 K
|
||||
// C = 1000 J/K
|
||||
const int n = 5;
|
||||
var c1 = new HeatContainer(1000f, 42f);
|
||||
var cA = new HeatContainer[n];
|
||||
|
||||
// Divide into n equal parts.
|
||||
HeatContainerHelpers.SplitAndCopy(ref c1, cA);
|
||||
|
||||
for (var i = 0; i < n; i++)
|
||||
{
|
||||
// The temperature should be the same as the initial one.
|
||||
// The heat capacities should be equally split.
|
||||
Assert.That(cA[i].Temperature, Is.EqualTo(42f));
|
||||
Assert.That(cA[i].HeatCapacity, Is.EqualTo(1000f / n).Within(1).Ulps);
|
||||
}
|
||||
|
||||
// Check that the initital container is unmodified.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(42f));
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(1000f));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Merge
|
||||
[Test]
|
||||
public void Merge2Test()
|
||||
{
|
||||
// T = 42 K
|
||||
// C = 5000 J/K
|
||||
var c1 = new HeatContainer(5000f, 42f);
|
||||
var energy1 = c1.InternalEnergy;
|
||||
// T = 100 K
|
||||
// C = 5000 J/K
|
||||
var c2 = new HeatContainer(5000f, 100f);
|
||||
var energy2 = c2.InternalEnergy;
|
||||
|
||||
// Merge 2 containers of the same capacity and different temperatures.
|
||||
HeatContainerHelpers.MergeInto(ref c1, ref c2);
|
||||
|
||||
// The temperature should be the average of the two initial ones.
|
||||
// The total heat capacity should be the sum of the two initial capacities.
|
||||
// The total energy should be conserved.
|
||||
Assert.That(c1.Temperature, Is.EqualTo((42f + 100f) / 2).Within(1).Ulps);
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(10000f).Within(1).Ulps);
|
||||
Assert.That(c1.InternalEnergy, Is.EqualTo(energy1 + energy2).Within(1).Ulps);
|
||||
|
||||
// The second container should remain unchanged.
|
||||
Assert.That(c2.Temperature, Is.EqualTo(100));
|
||||
Assert.That(c2.HeatCapacity, Is.EqualTo(5000f));
|
||||
|
||||
// T = 100 K
|
||||
// C = 750 J/K
|
||||
// E = 75000 J
|
||||
c1 = new HeatContainer(750f, 100f);
|
||||
energy1 = c1.InternalEnergy;
|
||||
// T = 300 K
|
||||
// C = 250 J/K
|
||||
// E = 75000 J
|
||||
c2 = new HeatContainer(250f, 300f);
|
||||
energy2 = c2.InternalEnergy;
|
||||
|
||||
// Merge 2 containers with different temperature and capacity.
|
||||
HeatContainerHelpers.MergeInto(ref c1, ref c2);
|
||||
|
||||
// The temperature should averaged weighted by capacity.
|
||||
// (100*750+300*250)/(750+250)=150
|
||||
// The total heat capacity should be the sum of the two initial capacities.
|
||||
// The total energy should be conserved.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(150f).Within(1).Ulps);
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(750f + 250f).Within(1).Ulps);
|
||||
Assert.That(c1.InternalEnergy, Is.EqualTo(energy1 + energy2).Within(1).Ulps);
|
||||
|
||||
// The second container should remain unchanged.
|
||||
Assert.That(c2.Temperature, Is.EqualTo(300));
|
||||
Assert.That(c2.HeatCapacity, Is.EqualTo(250f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Merge1PlusArrayTest()
|
||||
{
|
||||
// T = 200 K
|
||||
// C = 50 J/K
|
||||
var c1 = new HeatContainer(50f, 200f);
|
||||
var energy1 = c1.InternalEnergy;
|
||||
|
||||
// Array of 40 heat containers, each with
|
||||
// T = 100 K
|
||||
// C = 5 J/K
|
||||
const int n = 40;
|
||||
var cA1 = new HeatContainer(5f, 100);
|
||||
var cA = new HeatContainer[n];
|
||||
var energyA = cA1.InternalEnergy * n;
|
||||
for (var i = 0; i < cA.Length; i++)
|
||||
{
|
||||
cA[i] = cA1;
|
||||
}
|
||||
|
||||
// Merge the array into the single heat container.
|
||||
HeatContainerHelpers.MergeInto(ref c1, cA);
|
||||
|
||||
// The temperature should averaged weighted by capacity.
|
||||
// (200*50+100*5*40)/(50+5*40)=120
|
||||
// The total heat capacity should be the sum of the initial capacities.
|
||||
// The total energy should be conserved.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(120f).Within(1).Ulps);
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(50f + 5 * n).Within(1).Ulps);
|
||||
Assert.That(c1.InternalEnergy, Is.EqualTo(energy1 + energyA).Within(1).Ulps);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MergeArrayTest()
|
||||
{
|
||||
// This heat container will be overwritten.
|
||||
var c1 = new HeatContainer(50f, 200f);
|
||||
|
||||
// Array of 40 heat containers, each with
|
||||
// T = 100 K
|
||||
// C = 5 J/K
|
||||
const int n = 40;
|
||||
var cA1 = new HeatContainer(5f, 100);
|
||||
var cA = new HeatContainer[n];
|
||||
var energyA = cA1.InternalEnergy * n;
|
||||
for (var i = 0; i < cA.Length; i++)
|
||||
{
|
||||
cA[i] = cA1;
|
||||
}
|
||||
|
||||
// Merge the array into the single heat container.
|
||||
HeatContainerHelpers.MergeAndCopy(ref c1, cA);
|
||||
|
||||
// The temperature of all merged containers was the same.
|
||||
// The total heat capacity should be the sum of the initial capacities.
|
||||
// The total energy should be the sum of the intial energies.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(100f));
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(5 * n).Within(1).Ulps);
|
||||
Assert.That(c1.InternalEnergy, Is.EqualTo(energyA).Within(1).Ulps);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Exchange
|
||||
[Test]
|
||||
public void EquilibriumQuery2BodyTest()
|
||||
{
|
||||
// Cold c1, hot c2.
|
||||
var c1 = new HeatContainer(123f, 456f);
|
||||
var c2 = new HeatContainer(987f, 654f);
|
||||
|
||||
var dQ11 = HeatContainerHelpers.EquilibriumHeatQuery(ref c1, ref c1);
|
||||
var dQ12 = HeatContainerHelpers.EquilibriumHeatQuery(ref c1, ref c2);
|
||||
var dQ21 = HeatContainerHelpers.EquilibriumHeatQuery(ref c2, ref c1);
|
||||
var dQ22 = HeatContainerHelpers.EquilibriumHeatQuery(ref c2, ref c2);
|
||||
var t11 = HeatContainerHelpers.EquilibriumTemperatureQuery(ref c1, ref c1);
|
||||
var t12 = HeatContainerHelpers.EquilibriumTemperatureQuery(ref c1, ref c2);
|
||||
var t21 = HeatContainerHelpers.EquilibriumTemperatureQuery(ref c2, ref c1);
|
||||
var t22 = HeatContainerHelpers.EquilibriumTemperatureQuery(ref c2, ref c2);
|
||||
|
||||
// Containers should be in equilibrium with themselves.
|
||||
Assert.That(dQ11, Is.Zero.Within(1).Ulps);
|
||||
Assert.That(dQ22, Is.Zero.Within(1).Ulps);
|
||||
Assert.That(t11, Is.EqualTo(c1.Temperature).Within(1).Ulps);
|
||||
Assert.That(t22, Is.EqualTo(c2.Temperature).Within(1).Ulps);
|
||||
|
||||
// Heat should flow from hot to cold.
|
||||
Assert.That(dQ12, Is.LessThan(0f));
|
||||
Assert.That(dQ21, Is.GreaterThan(0f));
|
||||
Assert.That(t12, Is.LessThan(c2.Temperature));
|
||||
Assert.That(t21, Is.LessThan(c2.Temperature));
|
||||
Assert.That(t12, Is.GreaterThan(c1.Temperature));
|
||||
Assert.That(t21, Is.GreaterThan(c1.Temperature));
|
||||
// The result should be symmetric.
|
||||
Assert.That(dQ21, Is.EqualTo(-dQ12).Within(1).Ulps);
|
||||
Assert.That(t12, Is.EqualTo(t21).Within(1).Ulps);
|
||||
|
||||
// Check that the heat flow indeed brings them into equilibrium.
|
||||
HeatContainerHelpers.AddHeat(ref c1, -dQ12);
|
||||
HeatContainerHelpers.AddHeat(ref c2, dQ12);
|
||||
|
||||
Assert.That(c1.Temperature, Is.EqualTo(c2.Temperature).Within(1).Ulps);
|
||||
Assert.That(c1.Temperature, Is.EqualTo(t12).Within(1).Ulps);
|
||||
Assert.That(c1.Temperature, Is.EqualTo(t21).Within(1).Ulps);
|
||||
Assert.That(c2.Temperature, Is.EqualTo(t12).Within(1).Ulps);
|
||||
Assert.That(c2.Temperature, Is.EqualTo(t21).Within(1).Ulps);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Equilibrium2BodyTest()
|
||||
{
|
||||
// Cold c1, hot c2.
|
||||
var c1 = new HeatContainer(123f, 456f);
|
||||
var c2 = new HeatContainer(987f, 654f);
|
||||
var totalEnergy = c1.InternalEnergy + c2.InternalEnergy;
|
||||
|
||||
// Bring them into equilibrium.
|
||||
HeatContainerHelpers.Equilibrate(ref c1, ref c2);
|
||||
|
||||
// Total energy should be conserved.
|
||||
Assert.That(c1.InternalEnergy + c2.InternalEnergy, Is.EqualTo(totalEnergy).Within(1).Ulps);
|
||||
|
||||
// The temperature should be equal, the capacities unchanged.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(c2.Temperature).Within(1).Ulps);
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(123f));
|
||||
Assert.That(c2.HeatCapacity, Is.EqualTo(987f));
|
||||
|
||||
// Repeat with the out dQ overload.
|
||||
c1 = new HeatContainer(123f, 456f);
|
||||
c2 = new HeatContainer(987f, 654f);
|
||||
totalEnergy = c1.InternalEnergy + c2.InternalEnergy;
|
||||
var dQQuery = HeatContainerHelpers.EquilibriumHeatQuery(ref c1, ref c2);
|
||||
|
||||
// Bring them into equilibrium.
|
||||
HeatContainerHelpers.Equilibrate(ref c1, ref c2, out var dQresult);
|
||||
|
||||
// Total energy should be conserved.
|
||||
Assert.That(c1.InternalEnergy + c2.InternalEnergy, Is.EqualTo(totalEnergy).Within(1).Ulps);
|
||||
|
||||
// The temperature should be equal, the capacities unchanged.
|
||||
Assert.That(c1.Temperature, Is.EqualTo(c2.Temperature).Within(1).Ulps);
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(123f));
|
||||
Assert.That(c2.HeatCapacity, Is.EqualTo(987f));
|
||||
|
||||
// The output dQ should be the same as the query we did before.
|
||||
Assert.That(dQQuery, Is.EqualTo(dQresult).Within(1).Ulps);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Equilibrium3BodyTest()
|
||||
{
|
||||
// Cold c1, medium c2, hot c3.
|
||||
var c1 = new HeatContainer(300f, 123f);
|
||||
var c2 = new HeatContainer(200f, 234f);
|
||||
var c3 = new HeatContainer(100f, 456f);
|
||||
var totalEnergy = c1.InternalEnergy + c2.InternalEnergy + c3.InternalEnergy;
|
||||
|
||||
// Save as array.
|
||||
var cN = new HeatContainer[3];
|
||||
cN[0] = c1;
|
||||
cN[1] = c2;
|
||||
cN[2] = c3;
|
||||
|
||||
var tQuery = HeatContainerHelpers.EquilibriumTemperatureQuery(cN);
|
||||
var tQuerydQ = HeatContainerHelpers.EquilibriumTemperatureQuery(cN, out var dQQuery);
|
||||
|
||||
// Both queries should result in the same temperature.
|
||||
Assert.That(tQuery, Is.EqualTo(tQuerydQ).Within(1).Ulps);
|
||||
|
||||
// Heat flows from hot to cold.
|
||||
Assert.That(tQuery, Is.GreaterThan(c1.Temperature));
|
||||
Assert.That(tQuery, Is.LessThan(c3.Temperature));
|
||||
Assert.That(dQQuery[0], Is.GreaterThan(0f));
|
||||
Assert.That(dQQuery[2], Is.LessThan(0f));
|
||||
|
||||
// Total energy should be conserved.
|
||||
Assert.That(dQQuery.Sum(), Is.Zero.Within(1).Ulps);
|
||||
|
||||
// Check if we actually reach equilibrium with the calculated heat flow.
|
||||
HeatContainerHelpers.AddHeat(ref c1, dQQuery[0]);
|
||||
HeatContainerHelpers.AddHeat(ref c2, dQQuery[1]);
|
||||
HeatContainerHelpers.AddHeat(ref c3, dQQuery[2]);
|
||||
|
||||
Assert.That(c1.Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
Assert.That(c2.Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
Assert.That(c3.Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
|
||||
// Put the array into equilibrium.
|
||||
HeatContainerHelpers.Equilibrate(cN);
|
||||
|
||||
// Check if we actually reached equilibrium.
|
||||
Assert.That(cN[0].Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
Assert.That(cN[1].Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
Assert.That(cN[2].Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
|
||||
// Total energy should be conserved.
|
||||
var newTotalEnergy = cN[0].InternalEnergy + cN[1].InternalEnergy + cN[2].InternalEnergy;
|
||||
Assert.That(newTotalEnergy, Is.EqualTo(totalEnergy).Within(1).Ulps);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Equilibrium1Plus3BodyTest()
|
||||
{
|
||||
// Cold c1, medium c2 and c3, hot c4.
|
||||
var c1 = new HeatContainer(400f, 123f);
|
||||
var c2 = new HeatContainer(300f, 234f);
|
||||
var c3 = new HeatContainer(200f, 456f);
|
||||
var c4 = new HeatContainer(100f, 567f);
|
||||
var totalEnergy = c1.InternalEnergy + c2.InternalEnergy + c3.InternalEnergy + c4.InternalEnergy;
|
||||
|
||||
// Save as array.
|
||||
var cN = new HeatContainer[3];
|
||||
cN[0] = c1;
|
||||
cN[1] = c2;
|
||||
cN[2] = c3;
|
||||
|
||||
var tQuery = HeatContainerHelpers.EquilibriumTemperatureQuery(ref c4, cN);
|
||||
|
||||
// Heat flows from hot to cold.
|
||||
Assert.That(tQuery, Is.GreaterThan(c1.Temperature));
|
||||
Assert.That(tQuery, Is.LessThan(c4.Temperature));
|
||||
|
||||
// Total energy should be conserved.
|
||||
Assert.That(tQuery * (c1.HeatCapacity + c2.HeatCapacity + c3.HeatCapacity + c4.HeatCapacity), Is.EqualTo(totalEnergy).Within(1).Ulps);
|
||||
|
||||
// Put everything into equilibrium.
|
||||
HeatContainerHelpers.Equilibrate(ref c4, cN);
|
||||
|
||||
// Check if we actually reached equilibrium.
|
||||
Assert.That(cN[0].Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
Assert.That(cN[1].Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
Assert.That(cN[2].Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
Assert.That(c4.Temperature, Is.EqualTo(tQuery).Within(1).Ulps);
|
||||
|
||||
// Total energy should be conserved.
|
||||
var newTotalEnergy = cN[0].InternalEnergy + cN[1].InternalEnergy + cN[2].InternalEnergy + c4.InternalEnergy;
|
||||
Assert.That(newTotalEnergy, Is.EqualTo(totalEnergy).Within(1).Ulps);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Conduct
|
||||
[Test]
|
||||
public void Conduct1Test()
|
||||
{
|
||||
// T = 100 K
|
||||
// C = 42 J/K
|
||||
var c1 = new HeatContainer(42f, 100f);
|
||||
var c2 = new HeatContainer(42f, 100f);
|
||||
var c3 = new HeatContainer(42f, 100f);
|
||||
var c4 = new HeatContainer(42f, 100f);
|
||||
|
||||
// Conduct heat with a heat bath of 200K for a small time step of 0.01s and a conductance of 1.
|
||||
var dQ1 = HeatContainerHelpers.ConductHeat(ref c1, 200f, 0.01f, 100f);
|
||||
|
||||
// The temperature should be between 100 and 200K.
|
||||
// The heat capacity should be unchanged.
|
||||
Assert.That(c1.Temperature, Is.GreaterThan(100f));
|
||||
Assert.That(c1.Temperature, Is.LessThan(200f));
|
||||
Assert.That(c1.HeatCapacity, Is.EqualTo(42f));
|
||||
|
||||
// The conducted heat should positive, since the temperature got higher.
|
||||
Assert.That(dQ1, Is.GreaterThan(0f));
|
||||
|
||||
// Check that removing the heat again brings us back where we were originally.
|
||||
var c1Copy = c1;
|
||||
HeatContainerHelpers.AddHeat(ref c1Copy, -dQ1);
|
||||
Assert.That(c1Copy.Temperature, Is.EqualTo(100f).Within(1).Ulps);
|
||||
|
||||
// A greater temperature difference means a greater heat transfer.
|
||||
var dQ2 = HeatContainerHelpers.ConductHeat(ref c2, 300f, 0.01f, 100f);
|
||||
Assert.That(dQ2, Is.GreaterThan(dQ1));
|
||||
Assert.That(c2.Temperature, Is.GreaterThan(c1.Temperature));
|
||||
|
||||
// A greater time step means a greater heat transfer.
|
||||
var dQ3 = HeatContainerHelpers.ConductHeat(ref c3, 200f, 0.02f, 100f);
|
||||
Assert.That(dQ3, Is.GreaterThan(dQ1));
|
||||
Assert.That(c3.Temperature, Is.GreaterThan(c1.Temperature));
|
||||
|
||||
// A greater conductance means a greater heat transfer.
|
||||
var dQ4 = HeatContainerHelpers.ConductHeat(ref c4, 200f, 0.01f, 200f);
|
||||
Assert.That(dQ4, Is.GreaterThan(dQ1));
|
||||
Assert.That(c4.Temperature, Is.GreaterThan(c1.Temperature));
|
||||
|
||||
// Make sure we don't overshoot with a too large time step and conductance.
|
||||
var c5 = new HeatContainer(42f, 100f);
|
||||
var dQ5 = HeatContainerHelpers.ConductHeat(ref c5, 200f, 10f, 10000f);
|
||||
Assert.That(c5.Temperature, Is.EqualTo(200f).Within(1).Ulps);
|
||||
|
||||
// Check that the heat diff is still correct even when we would have overshot.
|
||||
HeatContainerHelpers.AddHeat(ref c5, -dQ5);
|
||||
Assert.That(c5.Temperature, Is.EqualTo(100f).Within(1).Ulps);
|
||||
|
||||
// Check that consecutive steps become smaller, but still get us closer to equilibrium.
|
||||
var c6 = new HeatContainer(42f, 100f);
|
||||
var t6Init = c6.Temperature;
|
||||
var dQ6A = HeatContainerHelpers.ConductHeat(ref c6, 200f, 1f, 1f);
|
||||
var t6A = c6.Temperature;
|
||||
var dQ6B = HeatContainerHelpers.ConductHeat(ref c6, 200f, 1f, 1f);
|
||||
var t6B = c6.Temperature;
|
||||
Assert.That(dQ6A, Is.GreaterThan(dQ6B));
|
||||
Assert.That(t6A, Is.GreaterThan(t6Init));
|
||||
Assert.That(t6B, Is.GreaterThan(t6A));
|
||||
|
||||
// Check that we converge towards the heat bath temperature.
|
||||
var c7 = new HeatContainer(42f, 100f);
|
||||
var e7Init = c7.InternalEnergy;
|
||||
var dQ7 = 0f;
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
dQ7 += HeatContainerHelpers.ConductHeat(ref c7, 200f, 1f, 1f);
|
||||
}
|
||||
Assert.That(c7.Temperature, Is.EqualTo(200f).Within(0.1).Percent);
|
||||
Assert.That(c7.InternalEnergy - dQ7, Is.EqualTo(e7Init).Within(0.2).Percent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Conduct2Test()
|
||||
{
|
||||
// Temperatures at 100 K and 200 K
|
||||
var cA = new HeatContainer(42f, 100f);
|
||||
var cB = new HeatContainer(123f, 200f);
|
||||
var totalEnergy = cA.InternalEnergy + cB.InternalEnergy;
|
||||
var tEquilibrium = HeatContainerHelpers.EquilibriumTemperatureQuery(ref cA, ref cB);
|
||||
var dQ = HeatContainerHelpers.ConductHeat(ref cA, ref cB, 1f, 1f);
|
||||
|
||||
// Heat flow from hot B to cold A should be positive.
|
||||
Assert.That(dQ, Is.GreaterThan(0f));
|
||||
|
||||
// Energy should be conserved.
|
||||
Assert.That(cA.InternalEnergy + cB.InternalEnergy, Is.EqualTo(totalEnergy));
|
||||
|
||||
// Check that we got closer to equilibrium, but did not reach it.
|
||||
Assert.That(cA.Temperature, Is.GreaterThan(100f));
|
||||
Assert.That(cA.Temperature, Is.LessThan(tEquilibrium));
|
||||
Assert.That(cB.Temperature, Is.LessThan(200f));
|
||||
Assert.That(cB.Temperature, Is.GreaterThan(tEquilibrium));
|
||||
|
||||
// Check that the given heat transfer amount is correct.
|
||||
HeatContainerHelpers.AddHeat(ref cA, -dQ);
|
||||
HeatContainerHelpers.AddHeat(ref cB, dQ);
|
||||
|
||||
Assert.That(cA.Temperature, Is.EqualTo(100f).Within(1).Ulps);
|
||||
Assert.That(cB.Temperature, Is.EqualTo(200f).Within(1).Ulps);
|
||||
|
||||
// Reset containers.
|
||||
cA = new HeatContainer(42f, 100f);
|
||||
cB = new HeatContainer(123f, 200f);
|
||||
|
||||
// Check that we converge towards equilibrium.
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
HeatContainerHelpers.ConductHeat(ref cA, ref cB, 1f, 1f);
|
||||
}
|
||||
Assert.That(cA.Temperature, Is.EqualTo(tEquilibrium).Within(0.1).Percent);
|
||||
Assert.That(cB.Temperature, Is.EqualTo(tEquilibrium).Within(0.1).Percent);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user