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

Memory Deallocation in FlorisModel.run method #926

Open
achenry opened this issue Jun 19, 2024 · 0 comments
Open

Memory Deallocation in FlorisModel.run method #926

achenry opened this issue Jun 19, 2024 · 0 comments

Comments

@achenry
Copy link

achenry commented Jun 19, 2024

It looks like there is a memory leak in the FlorisModel.run method i.e. if it is run multiple times sequentially, especially for a large number of wind speeds/directions, the total RAM usage increases steadily until the computer reaches its capacity, at which point most computers will issue a warning asking to shut down the process, and proceed to shut down if this is ignored.

Test with the memory_profiler tool, import the function decorator as :

from memory_profiler import profile

and then add the @profile decorator before the header of memory intensive functions. e.g. in floris.floris_model

@profile
def run(self) -> None:
...

For my use case, the memory leak traces were:
FlorisModel.run->floris.core.steady_state_atmospheric_condition() -> floris.core.solver.sequential_solver -> model_manager.deflection_model.function, calculate_transverse_velocity, model_manager.velocity_model.function

My workaround was to run floris in a subprocess (see this stackoverflow thread) , which is guaranteed to reallocate memory upon completion:

# define in global scope
def run_floris_proc(floris_env, turbine_powers_arr):
	floris_env.run()
	turbine_powers_arr = np.frombuffer(turbine_powers_arr, dtype=np.double, count=len(turbine_powers_arr))
	turbine_powers_arr[:] = floris_env.get_turbine_powers().flatten()

...
# within loop
turbine_powers_arr = RawArray('d', [0] * self.fi.env.core.flow_field.n_findex * self.n_turbines)
p = Process(target=run_floris_proc, args=(self.fi.env, self.turbine_powers_arr))
p.start()
p.join()
all_yawed_turbine_powers = np.frombuffer(self.turbine_powers_arr, dtype=np.double, count=len(self.turbine_powers_arr)).reshape((self.fi.env.core.flow_field.n_findex, self.n_turbines))                                      				

Another less reliable method is a combination of the del keyword and gc.collect() calls for redundant variables, but these do not guarantee memory deallocation if references still exist.

test_floris.py

import numpy as np
from memory_profiler import profile
from floris import FlorisModel, TimeSeries
 
@profile
def run_floris():
  # Load the Floris model
  fmodel = FlorisModel("inputs/gch.yaml")
   
  N = 100000
  np.random.seed(0)
   
  # Set up inflow wind conditions
  time_series = TimeSeries(
      wind_directions=270 + 30 * np.random.randn(N),
      wind_speeds=8 + 2 * np.random.randn(N),
      turbulence_intensities=0.06 + 0.02 * np.random.randn(N),
  )
   
  # Set the wind conditions for the model
  fmodel.set(wind_data=time_series)
   
  # Run the calculations
  fmodel.run()
  fmodel.run()
   
  # Extract turbine and farm powers
  turbine_powers = fmodel.get_turbine_powers() / 1000.0
  farm_power = fmodel.get_farm_power() / 1000.0
   
   print(turbine_powers.shape)
   print(farm_power.shape)

if __name__ == "__main__":
    run_floris()
python test_floris.py >> mem_prof.txt

The output of the memory profiling is:

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
     5    172.3 MiB    172.3 MiB           1   @profile
     6                                         def run_floris():
     7                                           # Load the Floris model
     8    173.2 MiB      0.9 MiB           1       fmodel = FlorisModel("/Users/ahenry/Documents/toolboxes/floris/examples/inputs/gch.yaml")
     9                                         
    10    173.2 MiB      0.0 MiB           1       N = 100000
    11    173.2 MiB      0.0 MiB           1       np.random.seed(0)
    12                                         
    13                                             # Set up inflow wind conditions
    14    175.3 MiB      0.0 MiB           2       time_series = TimeSeries(
    15    174.5 MiB      1.3 MiB           1           wind_directions=270 + 30 * np.random.randn(N),
    16    174.5 MiB      0.0 MiB           1           wind_speeds=8 + 2 * np.random.randn(N),
    17    175.3 MiB      0.8 MiB           1           turbulence_intensities=0.06 + 0.02 * np.random.randn(N),
    18                                             )
    19                                         
    20                                             # Set the wind conditions for the model
    21    534.0 MiB    358.7 MiB           1       fmodel.set(wind_data=time_series)
    22                                         
    23                                             # Run the calculations
    24   1721.0 MiB   1187.0 MiB           1       fmodel.run()
    25   1828.3 MiB    107.3 MiB           1       fmodel.run()
    26                                         
    27                                             # Extract turbine and farm powers
    28   1828.3 MiB      0.0 MiB           1       turbine_powers = fmodel.get_turbine_powers() / 1000.0
    29   1828.3 MiB      0.0 MiB           1       farm_power = fmodel.get_farm_power() / 1000.0
    30   1828.3 MiB      0.0 MiB           1       print(turbine_powers.shape)
    31   1828.3 MiB      0.0 MiB           1       print(farm_power.shape)

And if the run method is called repeatedly, in a loop for example, it will continue to use more and more memory.

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

No branches or pull requests

1 participant