Search Unity

Question Solving numerical equations and time synchronization problem

Discussion in 'Scripting' started by capocchione, Jul 2, 2020.

  1. capocchione

    capocchione

    Joined:
    Dec 9, 2019
    Posts:
    47
    Before the real question, some background on what I'm working on step by step also thanks to this forum (I'm a newbie in unity and c#)

    I'm trying to develop a simulation that handles a prefab shape/rotation changes following mathematical rules. To be more precise, it's a simulation of a tree growing following mathematical biological rules.

    Let's focus on one parameter of this growth. annualLengthGrowth is how much the prefab (a simple cylinder) "grows" (i.e. how much the prefab changes the y scale).

    annualLengthGrowth is called into the Growth script, updated every day that is equal to 1 second in Unity:

    Code (CSharp):
    1. annualLengthGrowth = Runge.runge(0.0f, 1.0f, nextValue, .1f, new Runge.Function(Equation.equation));
    2. annualGrowth = new Vector3(annualWidthGrowth, annualLengthGrowth, annualWidthGrowth);
    the Runge.runge() is the class I need to solve numerically my biological mathematical model that is an ODE
    The script Runge, is the implementation in C# of Runge-Kutta 4th order numerical method to solve ODEs:


    Code (CSharp):
    1. public class Runge
    2. {
    3.     //declare a delegate that takes a double and returns
    4.     public delegate float Function(float t, float annualGrowth);
    5.     public static float runge(float a, float b, float value, float step, Function f)
    6.     {
    7.         float t, w, k1, k2, k3, k4;
    8.         t = a;
    9.         w = value;
    10.         for (int i = 0; i < (b - a) / step; i++)
    11.         {
    12.             k1 = step * f(t, w);
    13.             k2 = step * f(t + step / 2f, w + k1 / 2f);
    14.             k3 = step * f(t + step / 2f, w + k2 / 2f);
    15.             k4 = step * f(t + step, w + k3);
    16.             w = w + (k1 + 2f * k2 + 2f * k3 + k4) / 6f;
    17.             t = a + i * step;          
    18.         }
    19.         return w;
    20.     }
    21. }
    where: a = initial point; b = final point, value = initial value, step = calculation step; Function f = ODE function to solve.
    k1, k2, k3, k4 are the four orders finite differences used by the numerical method to solve the ODE.

    The ODE function to solve is in another script, Equation.cs:

    Code (CSharp):
    1.     public static float equation(float t, float annualGrowth)
    2.     {
    3.         float y_prime;
    4.  
    5.         float maxLenght = setup.WExt * (1 - (setup.Manager.TotalLenght() / maxTreeHeight));
    6.         temp = (float)-Math.Pow(((180 - t) / 180), 2) * a + a;
    7.         alfaT = temp / a;
    8.         y_prime = alfaT * annualGrowth * (1 - (annualGrowth / maxLenght));
    9.         Debug.Log("t " + " " + t + " " + "temp " + " " + temp + " " + "alfaT " + alfaT);
    10.  
    11.         return y_prime;      
    12.     }
    Where setup.WExt is a fixed value i set from setup; setup.Manager.TotalLength() is the actual tree lenght, maxTreeHeight is the max tree height set in setup; a is a constant value (in this case a = 25f); maxLenght is the maximum lenght the prefab can reach and is set.

    Now, the t in temp calculation is the day I calculate it. It should be a number from 1 to 365 and then, If the simulation continues, back to 1.
    I think it should be implemented something like this:


    Code (CSharp):
    1. var minRange = 1;
    2. var maxRange = 365;
    3.      
    4. for (int i = 0; i < simulationTime; i++)
    5. {
    6.     var t= i % (maxRange - minRange + 1) + minRange;
    7. }
    The Debug.Log in the equation script above, returns:

    I assume that it's calculating every frame and not every timestep I want (1 second in unity = 1 day for me)

    at t = 10, for example, the calculation are numerically right so the calculus works fine (numerically):


    To sum up, I should come with a solution that is something like this table and I don't know how to do it:

    Code (CSharp):
    1. // ╔════════════════════╦══════════════╦═════════╦════════╗
    2. // ║ Simulation seconds ║ ODE Solution ║ t (day) ║ alfaT  ║
    3. // ╠════════════════════╬══════════════╬═════════╬════════╣
    4. // ║                  1 ║ number       ║       1 ║ number ║
    5. // ║                  2 ║ number       ║       2 ║ number ║
    6. // ║                  3 ║ number       ║       3 ║ number ║
    7. // ║                ... ║ ...          ║     ... ║ ...    ║
    8. // ║                365 ║ number       ║     365 ║ number ║
    9. // ║                366 ║ number       ║       1 ║ number ║
    10. // ║                367 ║ number       ║       2 ║ number ║
    11. // ║                ... ║ ...          ║     ... ║ ...    ║
    12. // ╚════════════════════╩══════════════╩═════════╩════════╝
    I really appreciate all the patience and help you're giving me on this forum, so far it's been super helpful.
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    Ok I definitely didn't follow all that math nor do I understand the algorithm you're implementing at all, but what I got from your thread is that you have some discrete-timestep-based iterative algorithm that you can feed a timestep into and it will simulate that much time in your algorithm. (Something to do with trees growing??)

    Anyway Unity gives us the Update() method which runs once per frame. They also give us the handy Time.deltaTime property which tells us how much time has passed each frame (this will be a small number like 1/60th of a second). So you can do something like this:

    Code (CSharp):
    1. public float RealTimestep = 1f; // 1 second in realtime
    2. public float SimulationTimestep = 86400f; // 1 day in simulation time
    3.  
    4. // Ratio of realtime to simulation time.
    5. float simulationRatio;
    6. // T represents total time that has passed within the simulation (not realtime)
    7. float t = 0;
    8.  
    9. void Start() {
    10.   simulationRatio = SimulationTimestep / RealTimestep;
    11.   t = 0;
    12. }
    13.  
    14. void Update() {
    15.   float timeElapsed = Time.deltaTime * simulationRatio;
    16.   t += timeElapsed;
    17.  
    18.   // Not sure if you need to plug in timeElapsed, which is the amount of simulation time that has passed since the previous frame, or t which is the total simulation time.
    19.   Simulate(timeElapsed);
    20. }
    "Simulate" is whatever your function is that advances your simulation. Maybe it's your "runge" function? Anyway what I've given you is a way to structure the program in a Monoehaviour and get the time values to pass into your simulation.
     
    capocchione likes this.
  3. capocchione

    capocchione

    Joined:
    Dec 9, 2019
    Posts:
    47
    I do have into the scene a TimeManager empty object with timemanager.cs script attached (modified with your suggestions):


    Code (csharp):
    1. public class TimeManager : MonoBehaviour
    2. {
    3.  
    4.     bool dayElapsed;
    5.     public float RealTimestep = 1f; // 1 second in realtime
    6.     public float SimulationTimestep = 86400f; // 1 day in simulation time
    7.     float simulationRatio; // Ratio of realtime to simulation time.
    8.     float t = 0; // T represents total time that has passed within the simulation (not realtime)
    9.  
    10.     public bool IsDayElapsed
    11.     {
    12.         get { return dayElapsed; }
    13.         set { dayElapsed = value; }
    14.     }
    15.  
    16.     private void Start()
    17.     {
    18.         simulationRatio = SimulationTimestep / RealTimestep;
    19.         t = 0;
    20.     }
    21.     void Update()
    22.     {
    23.         float timeElapsed = Time.deltaTime * simulationRatio;
    24.         t += timeElapsed;
    25.         Debug.Log("t = " + t + " " + "timeElapsed " + timeElapsed);
    26.        
    27.         dayElapsed = false;
    28.         if (timeElapsed >= RealTimestep)
    29.         {          
    30.             dayElapsed = true;
    31.         }
    32.     }
    33. }
    The bool dayElapsed is called by the growth update that is the main growing script:


    Code (CSharp):
    1. public void UpdateGrowth()
    2.     {            
    3.         if (timeManager.IsDayElapsed && timeManager.ElapsedDays <= setup.SimulationTime)
    4.         {
    5. //growing stuff
    6.         }
    Where setup.SimulationTime is the time set to run the simulation (for example, 30 days)
    The Debug.Log into the TimeManager Update() returns:


    It should be t = 1, 2, 3 and so on, right? Because 1 second = 1 day
     
    Last edited: Jul 3, 2020