From 8f781f40e2ca755739ac452e86ecac084f4772ff Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Fri, 8 Nov 2024 14:48:02 +0100 Subject: [PATCH 01/12] (+) parameter nu_max_time to shut off neutrinos This parameter can be useful for e.g., CCSN simulations because at the moment winnet will just keep nu-luminosities and nu-temperatures constant after the simulation has ended. If the tracer is not flying away (i.e., not increasing the radius) this can lead to a very high neutrino radiation over time. For CCSN this parameter should be maybe set to 1d2, per default it is -1 and off. --- doc/doxygen/doxygen_pages/parameters.dox | 12 +++++ par/template.par | 4 ++ src/jacobian_class.f90 | 57 +++++++++++++++++++----- src/parameter_class.f90 | 6 +++ src/timestep_module.f90 | 10 ++++- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/doc/doxygen/doxygen_pages/parameters.dox b/doc/doxygen/doxygen_pages/parameters.dox index ce59e71f..7dbec982 100644 --- a/doc/doxygen/doxygen_pages/parameters.dox +++ b/doc/doxygen/doxygen_pages/parameters.dox @@ -1456,6 +1456,18 @@ An example file looks like: \ref use_thermal_nu_loss, \ref h_nu_loss_every + \section nu_max_time nu_max_time + Specifies the time limit in seconds for neutrino reactions. Neutrino interactions will be disabled after this duration. The default value is 100s. + A value of -1 keeps neutrino reactions active indefinitely. Note that this is necessary as WinNet keeps the neutrino quantities constant after the + trajectory has ended. + + @par Example template line: + `` nu_max_time= 10.0 `` + + @see parameter_class::nu_max_time, \ref nuflag, [Roberts \& Reddy 2017](https://ui.adsabs.harvard.edu/abs/2017hsn..book.1605R/abstract) + + + \section nunucleo_rates_file nunucleo_rates_file Path to a file, containing neutrino capture rates on protons and neutrons. The file contains tabulated values of the rates, using a neutrino diff --git a/par/template.par b/par/template.par index 41cbe9ad..5962b000 100644 --- a/par/template.par +++ b/par/template.par @@ -265,6 +265,10 @@ nurates_file = # has also to contain the neutrino information. neutrino_mode = +# Maximum time (in s) for neutrino reactions to occure. +# The default is 100s. Set -1 to never turn off neutrino reactions. +nu_max_time = + ### Case: neutrino_mode = 'analytic' ### diff --git a/src/jacobian_class.f90 b/src/jacobian_class.f90 index 046c4529..0b4fffa4 100644 --- a/src/jacobian_class.f90 +++ b/src/jacobian_class.f90 @@ -25,6 +25,7 @@ module jacobian_class real(r_kind),parameter,private :: rate_min_cutoff = 1d-50 !< Minimum cutoff for the reaction rates real(r_kind),parameter,private :: rate_max_cutoff = 1d99 !< Maximum cutoff for the reaction rates logical,private :: freeze_rate_indicator = .False. !< Indicator for debug statement + logical,private :: nu_shutoff_indicator = .False. !< Indicator for debug statement real(r_kind),private :: old_time=-99, old_temp=-99, old_dens=-99, old_ye=-99, old_rad=-99 ! ! Public and private fields and methods of the module (all routines are publis) @@ -120,8 +121,8 @@ subroutine calculate_reaction_rate(time, temp, rho, Ye, rkm, rrate_array, idx, r mue = mue + unit%mass_e end if - ! Interpolate neutrino quantities at given time - if (nuflag.ge.1) then + ! ! Interpolate neutrino quantities at given time + if ((nuflag.ge.1) .and. ((time .lt. nu_max_time) .or. (nu_max_time .eq. -1d0))) then call nuflux(time, rkm) ! returns fluxnu(4) call nutemp(time) ! returns tempnu(4) call nucs() @@ -288,6 +289,7 @@ end subroutine calculate_reaction_rate subroutine abchange (time, itemp, rho, Ye, rkm, Y, dYdt, evolution_mode) use global_class, only: nreac, rrate, net_names use fission_rate_module, only: fissrate, nfiss, fiss_neglect + use parameter_class, only: nuflag, nu_max_time implicit none real(r_kind),intent(in) :: time !< time [s] @@ -319,6 +321,16 @@ subroutine abchange (time, itemp, rho, Ye, rkm, Y, dYdt, evolution_mode) temp = itemp end if + ! Shut off neutrinos + if (nuflag .ge. 1) then + if ((time .gt. nu_max_time) .and. (nu_max_time .ne. -1d0)) then + if (.not. nu_shutoff_indicator) then + print *,"Shutting off neutrino reactions at "//num_to_str(nu_max_time)//" s" + nu_shutoff_indicator = .True. + end if + end if + end if + ! Be sure to call the reaction rate calculation at least ones to initialize ! (important for fission later on) call calculate_reaction_rate(time, temp, rho, Ye, rkm) @@ -329,15 +341,18 @@ subroutine abchange (time, itemp, rho, Ye, rkm, Y, dYdt, evolution_mode) ! Only consider weak reactions in NSE if ((evolution_mode.eq.EM_NSE).and.(rrate(i)%is_weak.eqv..false.)) cycle outer + ! Check if neutrino rates are still necessary + if ((nuflag .ge. 1) .and. (nu_max_time .ne. -1d0) .and. (time .ge. nu_max_time) .and. & + (rrate(i)%reac_src .eq. rrs_nu)) then + cycle outer + end if + ! Calculate the reaction rate call calculate_reaction_rate(time, temp, rho, Ye, rkm, rrate, i, rat) ! Check if one can ignore the rate if (skip_rate(rat, rrate(i),Y)) cycle outer - ! Ignore negligible rates - if (rat .lt. rate_min_cutoff) cycle outer - ! Don't do fission if ((rrate(i)%reac_type .eq. rrt_nf) .or. & (rrate(i)%reac_type .eq. rrt_bf) .or. & @@ -473,7 +488,8 @@ function skip_rate(rat, rrate_in,Y) result(res) integer :: zero_count res = .false. - ! Ignore negligible rates + + ! Ignore negligible rates if (rat .lt. rate_min_cutoff) res = .True. ! Don't do fission @@ -526,8 +542,8 @@ subroutine jacobi_init (time, itemp, rho, rkm, Y, Y_p, dYdt, rhs, h, evolution_m use pardiso_class, only: dia, pt_e, pt_b, rows use gear_module, only: get_l1, get_predictor_Y, get_predictor_dYdt use nuclear_heating, only: calculate_qdot, reset_qdot - use screening_module, only: iscreen, hv - use parameter_class, only: heating_mode + use parameter_class, only: nu_max_time, nuflag + use screening_module, only: hv, iscreen real(r_kind),intent(in) :: time !< time [s] real(r_kind),intent(in) :: itemp !< initial temperature in units of 10**9 K @@ -596,6 +612,16 @@ subroutine jacobi_init (time, itemp, rho, rkm, Y, Y_p, dYdt, rhs, h, evolution_m temp = itemp end if + ! Shut off neutrinos + if (nuflag .ge. 1) then + if ((time .gt. nu_max_time) .and. (nu_max_time .ne. -1d0)) then + if (.not. nu_shutoff_indicator) then + print *,"Shutting off neutrino reactions at "//num_to_str(nu_max_time)//" s" + nu_shutoff_indicator = .True. + end if + end if + end if + ! Initialize dydt and vals vals = 0.d0 dYdt = 0.d0 @@ -624,6 +650,13 @@ subroutine jacobi_init (time, itemp, rho, rkm, Y, Y_p, dYdt, rhs, h, evolution_m ! Consider only weak reactions in NSE if ((evolution_mode.eq.EM_NSE).and.(rr_tmp%is_weak.eqv..false.)) cycle outer + ! Check if neutrino rates are still necessary + if ((nuflag .ge. 1) .and. (nu_max_time .ne. -1d0) .and. (time .ge. nu_max_time) .and. & + (rr_tmp%reac_src .eq. rrs_nu)) then + cycle outer + end if + + ! Calculate the reaction rate call calculate_reaction_rate(time, temp, rho, Ye, rkm, rrate, i, rat) @@ -632,7 +665,7 @@ subroutine jacobi_init (time, itemp, rho, rkm, Y, Y_p, dYdt, rhs, h, evolution_m ! Create the jacobian and the DGLs select case (rr_tmp%group) - ! Chapter 1-3: one educt + ! Chapter 1-3: one reactant case(1:3,11) do j = 1, 5 if (rr_tmp%parts(j) .eq. 0) exit @@ -642,7 +675,7 @@ subroutine jacobi_init (time, itemp, rho, rkm, Y, Y_p, dYdt, rhs, h, evolution_m vals(rr_tmp%cscf_ind(1,j)) = vals(rr_tmp%cscf_ind(1,j)) - rat * & rr_tmp%ch_amount(j) end do - ! Chapter 1-4: two educts + ! Chapter 1-4: two reactants case(4:7) do j = 1, 6 if (rr_tmp%parts(j) .eq. 0) exit @@ -655,7 +688,7 @@ subroutine jacobi_init (time, itemp, rho, rkm, Y, Y_p, dYdt, rhs, h, evolution_m vals(rr_tmp%cscf_ind(2,j)) = vals(rr_tmp%cscf_ind(2,j)) - rat * & rr_tmp%ch_amount(j) * Y(rr_tmp%parts(1)) end do - ! Chapter 8-9: three educts + ! Chapter 8-9: three reactants case(8:9) do j = 1, 6 if (rr_tmp%parts(j) .eq. 0) exit @@ -673,7 +706,7 @@ subroutine jacobi_init (time, itemp, rho, rkm, Y, Y_p, dYdt, rhs, h, evolution_m rr_tmp%ch_amount(j) * Y(rr_tmp%parts(1)) * & Y(rr_tmp%parts(2)) end do - ! Chapter 10: four educts + ! Chapter 10: four reactants case(10) do j = 1, 6 if (rr_tmp%parts(j) .eq. 0) exit diff --git a/src/parameter_class.f90 b/src/parameter_class.f90 index 7b2ff45d..77109540 100644 --- a/src/parameter_class.f90 +++ b/src/parameter_class.f90 @@ -195,6 +195,7 @@ module parameter_class character(max_fname_len):: Lxbar !< Muon and Tauon antineutrino luminosities [erg/s] character(max_fname_len):: Enux !< average Muon and Tauon neutrino energies [MeV] character(max_fname_len):: Enuxbar !< average Muon and Tauon antineutrino energies [MeV] + real(r_kind) :: nu_max_time !< Maximum time for neutrino reactions to react character(max_fname_len):: prepared_network_path !< Prepared network folder !>-- Newton-Raphson iterative loop parameters @@ -404,6 +405,7 @@ subroutine set_param(param_name,param_value) ":final_temp"// & ":final_dens" // & ":initial_stepsize"// & + ":nu_max_time"// & ":freeze_rate_temp"// & ":nse_nr_tol"// & ":nse_delt_t9min" // & @@ -653,6 +655,8 @@ subroutine set_param(param_name,param_value) nr_tol= read_float_param(str_value,param_name) elseif(param_name.eq."freeze_rate_temp") then freeze_rate_temp= read_float_param(str_value,param_name) + elseif(param_name.eq."nu_max_time") then + nu_max_time= read_float_param(str_value,param_name) !--- logical type parameters elseif(param_name.eq."read_initial_composition") then read_initial_composition= lparam_value @@ -973,6 +977,7 @@ subroutine set_default_param nsetemp_hot = 8.e0 nsetemp_cold = 7.e0 nuflag = 0 + nu_max_time = -1d0 ! For CCSN 1d2 may be good e.g. Roberts & Reddy 2017 (https://ui.adsabs.harvard.edu/abs/2017hsn..book.1605R/abstract) neutrino_mode = 'analytic' nuchannel_file = trim(adjustl(win_path))//"nu_channels" nu_loss_every = 0 @@ -1149,6 +1154,7 @@ subroutine output_param write(ofile,'(A,I1)') 'nuflag = ' , nuflag write(ofile,'(3A)') 'nuchannel_file = "', trim(nuchannel_file),'"' write(ofile,'(A,I5)') 'nu_loss_every = ' , nu_loss_every + write(ofile,'(A,es14.7)') 'nu_max_time = ' , nu_max_time write(ofile,'(3A)') 'nunucleo_rates_file = "', trim(nunucleo_rates_file),'"' write(ofile,'(3A)') 'nurates_file = "', trim(nurates_file),'"' write(ofile,'(A,I5)') 'out_every = ' , out_every diff --git a/src/timestep_module.f90 b/src/timestep_module.f90 index b7369e9e..574937c0 100644 --- a/src/timestep_module.f90 +++ b/src/timestep_module.f90 @@ -142,7 +142,7 @@ subroutine restrict_timestep(ctime,h,temp,dens,ttemp,tdens) termination_criterion,final_dens,final_temp,final_time,& custom_snapshots,trajectory_mode,heating_mode, & nsetemp_cold,T9_analytic,rho_analytic,solver,h_custom_snapshots, & - heating_density + heating_density, nu_max_time, nuflag use gear_module, only: get_timestep, set_timestep use hydro_trajectory, only: ztime,zsteps,ztime,ztemp,zdens use analysis, only: snapshot_time,snapshot_amount @@ -312,6 +312,14 @@ subroutine restrict_timestep(ctime,h,temp,dens,ttemp,tdens) end if end if + !-- Restrict timestep to switch off neutrinos + if (nuflag .ge. 1) then + if ((ctime .lt.nu_max_time) .and. (ctime+h .gt. nu_max_time)) then + h = max(nu_max_time-ctime,1e-15) + end if + end if + + ! Also gear should be regulated by the restriction so change the timestep if ((solver == 1) .and. (h_in .ne. h)) then call set_timestep(h) From e7940d0ed4eaa327828637787c2021a4bf3e903a Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Fri, 8 Nov 2024 14:52:48 +0100 Subject: [PATCH 02/12] (*) docu Changed the description of default parameter from nu_max_time. --- doc/doxygen/doxygen_pages/parameters.dox | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/doxygen/doxygen_pages/parameters.dox b/doc/doxygen/doxygen_pages/parameters.dox index 7dbec982..abc0efce 100644 --- a/doc/doxygen/doxygen_pages/parameters.dox +++ b/doc/doxygen/doxygen_pages/parameters.dox @@ -1457,8 +1457,8 @@ An example file looks like: \section nu_max_time nu_max_time - Specifies the time limit in seconds for neutrino reactions. Neutrino interactions will be disabled after this duration. The default value is 100s. - A value of -1 keeps neutrino reactions active indefinitely. Note that this is necessary as WinNet keeps the neutrino quantities constant after the + Specifies the time limit in seconds for neutrino reactions. Neutrino interactions will be disabled after this duration. + A value of -1 keeps neutrino reactions active indefinitely (default). Note that this can be necessary as WinNet keeps the neutrino quantities constant after the trajectory has ended. @par Example template line: From e28c6dc71a64ed9ec9b5b27db502cb07cf0034f8 Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Fri, 8 Nov 2024 15:19:31 +0100 Subject: [PATCH 03/12] (+) WinNet movie got time slider. Now, thanks to H. Rose, there is a timeslider option for the WinNet movie. When creating a movie you can add --slider to add it and it makes it easier to jump to specific frames. --- bin/movie_script/README.md | 107 ++++++++++---------- bin/movie_script/src_files/FlowAnimation.py | 89 +++++++++++++++- bin/movie_script/winnet_movie.py | 4 + 3 files changed, 143 insertions(+), 57 deletions(-) mode change 100755 => 100644 bin/movie_script/winnet_movie.py diff --git a/bin/movie_script/README.md b/bin/movie_script/README.md index 729bf05e..60584211 100644 --- a/bin/movie_script/README.md +++ b/bin/movie_script/README.md @@ -2,152 +2,155 @@ This folder contains a script to analyze a WinNet run. It will show or save a movie of the mass fractions over time in the nuclear chart, inspired by the movie of SkyNet (done by J. Lippuner). -To create a video, the run must have snapshot output enabled (parameters [snapshots_every](https://nuc-astro.github.io/WinNet/parameters.html#h_snapshot_every) or [h_snapshots_every](https://nuc-astro.github.io/WinNet/parameters.html#h_snapshot_every)). -Also timescales ([timescales_every](https://nuc-astro.github.io/WinNet/parameters.html#timescales_every) or [h_timescales_every](https://nuc-astro.github.io/WinNet/parameters.html#h_timescales_every)), -mainout properties ([mainout_every](https://nuc-astro.github.io/WinNet/parameters.html#mainout_every) or [h_mainout_every](https://nuc-astro.github.io/WinNet/parameters.html#h_mainout_every)), -energy generation ([engen_every](https://nuc-astro.github.io/WinNet/parameters.html#engen_every) or [h_engen_every](https://nuc-astro.github.io/WinNet/parameters.html#h_engen_every)), -tracked nuclei ([track_nuclei_every](https://nuc-astro.github.io/WinNet/parameters.html#track_nuclei_every) or [h_track_nuclei_every](https://nuc-astro.github.io/WinNet/parameters.html#h_track_nuclei_every)), +To create a video, the run must have snapshot output enabled (parameters [snapshots_every](https://nuc-astro.github.io/WinNet/parameters.html#h_snapshot_every) or [h_snapshots_every](https://nuc-astro.github.io/WinNet/parameters.html#h_snapshot_every)). +Also timescales ([timescales_every](https://nuc-astro.github.io/WinNet/parameters.html#timescales_every) or [h_timescales_every](https://nuc-astro.github.io/WinNet/parameters.html#h_timescales_every)), +mainout properties ([mainout_every](https://nuc-astro.github.io/WinNet/parameters.html#mainout_every) or [h_mainout_every](https://nuc-astro.github.io/WinNet/parameters.html#h_mainout_every)), +energy generation ([engen_every](https://nuc-astro.github.io/WinNet/parameters.html#engen_every) or [h_engen_every](https://nuc-astro.github.io/WinNet/parameters.html#h_engen_every)), +tracked nuclei ([track_nuclei_every](https://nuc-astro.github.io/WinNet/parameters.html#track_nuclei_every) or [h_track_nuclei_every](https://nuc-astro.github.io/WinNet/parameters.html#h_track_nuclei_every)), or abundance flows ([flow_every](https://nuc-astro.github.io/WinNet/parameters.html#flow_every) or [h_flow_every](https://nuc-astro.github.io/WinNet/parameters.html#h_flow_every)) -can be plotted if the frequency of the output is **set to the same value as the one of the snapshots**. +can be plotted if the frequency of the output is **set to the same value as the one of the snapshots**. An example command to generate the video is: ```bash python winnet_movie.py -i ../../runs/Example_NSM_dyn_ejecta_rosswog -``` +``` This command will display the movie in a separate window. Note that this process may be slow as the movie is generated in real-time. For a smoother experience, you can save the video to a file using the --save option. #### Options -- `-h`, `--help` +- `-h`, `--help` Show this help message and exit. -- `-i RUNDIR`, `--input=RUNDIR` +- `-i RUNDIR`, `--input=RUNDIR` Simulation directory to visualize (default: current directory). -- `--disable_flow` +- `--disable_flow` Whether or not to plot the flow arrows. -- `--flow_min=FLOW_MIN` +- `--flow_min=FLOW_MIN` Lower limit of the flow. -- `--flow_max=FLOW_MAX` +- `--flow_max=FLOW_MAX` Upper limit of the flow. -- `--fix_flows` +- `--fix_flows` Whether or not the flows are adapted to the data or lie between `flow_min` and `flow_max`. -- `--flow_range=FLOW_RANGE` +- `--flow_range=FLOW_RANGE` Log range of the flows in case they are not fixed. -- `--fix_flow_arrow_width` +- `--fix_flow_arrow_width` Fix the width of the flow arrows to a constant width. -- `--flow_cmap=FLOW_CMAP` +- `--flow_cmap=FLOW_CMAP` Colormap of the flows. -- `--separate_fission` +- `--separate_fission` Whether or not to show arrows also for fission. If not present, hatched areas will be plotted. -- `--fission_minflow=FISSION_MINFLOW` +- `--fission_minflow=FISSION_MINFLOW` Minimum flow to get indicated as a fission region in case the separate fission flag is not given. -- `--x_min=X_MIN` +- `--x_min=X_MIN` Lower limit of the mass fraction. -- `--x_max=X_MAX` +- `--x_max=X_MAX` Upper limit of the mass fraction. -- `--x_cmap=X_CMAP` +- `--x_cmap=X_CMAP` Colormap of the mass fractions. -- `--disable_abar` +- `--disable_abar` Whether or not disabling the indication of Abar. -- `--mass_bins_cmap=MASS_BINS_CMAP` +- `--mass_bins_cmap=MASS_BINS_CMAP` Colormap of the background colors. -- `--disable_magic` +- `--disable_magic` Whether or not disabling the indication for the magic number. -- `--additional_plot=ADDITIONAL_PLOT` - Whether or not to show an additional plot in the top left corner. Possible options are 'timescales', 'tracked', or 'energy' +- `--additional_plot=ADDITIONAL_PLOT` + Whether or not to show an additional plot in the top left corner. Possible options are 'timescales', 'tracked', or 'energy' for plotting average timescales, mass fractions of tracked nuclei, or nuclear energy generation. -- `--tau_min=TAU_MIN` +- `--tau_min=TAU_MIN` Lower limit of the average timescales. -- `--tau_max=TAU_MAX` +- `--tau_max=TAU_MAX` Upper limit of the average timescales. -- `--engen_min=ENGEN_MIN` +- `--engen_min=ENGEN_MIN` Lower limit of the Energy. -- `--engen_max=ENGEN_MAX` +- `--engen_max=ENGEN_MAX` Upper limit of the Energy. -- `--tracked_min=TRACKED_MIN` +- `--tracked_min=TRACKED_MIN` Lower limit of the tracked nuclei mass fractions. -- `--tracked_max=TRACKED_MAX` +- `--tracked_max=TRACKED_MAX` Upper limit of the tracked nuclei mass fractions. -- `--time_min=T_MIN` +- `--time_min=T_MIN` Lower limit of the time. -- `--time_max=T_MAX` +- `--time_max=T_MAX` Upper limit of the time. -- `--disable_mainout` +- `--disable_mainout` Whether or not disabling the mainout plot. -- `--density_min=DENSITY_MIN` +- `--density_min=DENSITY_MIN` Lower limit of the density. -- `--density_max=DENSITY_MAX` +- `--density_max=DENSITY_MAX` Upper limit of the density. -- `--temperature_min=TEMPERATURE_MIN` +- `--temperature_min=TEMPERATURE_MIN` Lower limit of the temperatures. -- `--temperature_max=TEMPERATURE_MAX` +- `--temperature_max=TEMPERATURE_MAX` Upper limit of the temperature. -- `--ye_min=YE_MIN` +- `--ye_min=YE_MIN` Lower limit of the electron fraction. -- `--ye_max=YE_MAX` +- `--ye_max=YE_MAX` Upper limit of the electron fraction. -- `--frame_min=FRAME_MIN` +- `--frame_min=FRAME_MIN` Value of the first frame (default: 1). -- `--frame_max=FRAME_MAX` +- `--frame_max=FRAME_MAX` Value of the last frame (default: end of the simulation). -- `--save` +- `--slider` + Whether or not to add a slider to the simulation to jump to specific frames. + +- `--save` Whether or not saving the movie. -- `--save_frames` +- `--save_frames` Whether or not saving the frames of the movie, not compatible with the save option. -- `--output=OUTPUT_NAME` +- `--output=OUTPUT_NAME` Output name of the movie. -- `--parallel_save` +- `--parallel_save` Whether or not to save the movie or frames in parallel. -- `--parallel_cpus=PARALLEL_CPUS` +- `--parallel_cpus=PARALLEL_CPUS` Number of CPUs to use for parallel saving. -- `--interval=INTERVAL` +- `--interval=INTERVAL` Interval of the movie (larger value equals slower). -- `--mpirun_path=MPIRUN_PATH` +- `--mpirun_path=MPIRUN_PATH` Path of the `mpirun` command to use for parallel saving. - - + + #### Example An example output could look like the following: diff --git a/bin/movie_script/src_files/FlowAnimation.py b/bin/movie_script/src_files/FlowAnimation.py index 3b815af1..bbfe774d 100644 --- a/bin/movie_script/src_files/FlowAnimation.py +++ b/bin/movie_script/src_files/FlowAnimation.py @@ -13,6 +13,7 @@ from matplotlib.colors import LogNorm, SymLogNorm from matplotlib.colors import ListedColormap, LinearSegmentedColormap from matplotlib.animation import FuncAnimation +from matplotlib.widgets import Slider from wreader import wreader from h5py import File from nucleus_multiple_class import nucleus_multiple @@ -73,7 +74,8 @@ def __init__( densityrange = (1e-5, 1e12), temperaturerange = (0, 10), yerange = (0.0, 0.55), - plot_logo = True + plot_logo = True, + slider = False ): """ Parameters @@ -152,6 +154,8 @@ def __init__( Range of the electron fraction axis in the mainout plot. plot_logo : bool Plot the WinNet logo. + slider : bool + Use a slider widget. """ @@ -194,6 +198,7 @@ def __init__( self.timerange = timerange # Range of the time axis self.plot_mainout = plot_mainout # Plot the mainout data self.plot_logo = plot_logo # Plot the WinNet logo + self.use_slider = slider # Use slider widget self.flow_group = 'flows' # Name of the group in the HDF5 file that contains the flow data self.plot_flow = plot_flow # Plot the flow of the abundances self.fig = fig # Figure to plot the animation on @@ -332,6 +337,16 @@ def init_axes(self): if self.plot_logo: self.__init_logo() + # Slider + if self.use_slider: + self.__init_slider() + + + self.movie_paused = False + + self.fig.canvas.mpl_connect('button_press_event', self.pause_movie) + self.fig.canvas.mpl_connect('key_press_event', self.arrow_update) + def __init_nucchart_ax(self): """ @@ -449,6 +464,10 @@ def __init_logo(self): self.axLogo.axis('off') self.axLogo.imshow(plt.imread(os.path.join(self.__data_path,'WinNet_logo.png'))) + def __init_slider(self): + + self.ax_slider = plt.axes([0.1, 0.1, 0.8, 0.05], facecolor='lightgoldenrodyellow') + def init_data(self): """ @@ -539,7 +558,6 @@ def init_plot(self): """ Initialize the plots. """ - # Plot the nuclear chart self.abun_im = self.ax.pcolormesh(self.n,self.z,self.abun.T, cmap = self.__abundance_colors,vmin=(min(self.values)), @@ -561,7 +579,10 @@ def init_plot(self): # Create patchcollection of arrows width = (self.flow_maxArrowWidth-self.flow_minArrowWidth)/self.flow_prange with np.errstate(divide='ignore'): - arrowwidth = (np.log10(self.flow)-np.log10(self.flow_min))*width + self.flow_minArrowWidth + arrowwidth = (np.log10(self.flow)-np.log10(self.flow_min))*width + arrowwidth = np.maximum(arrowwidth, self.flow_minArrowWidth) + + flow_arrows = [Arrow(self.flow_N[i],self.flow_Z[i],self.flow_dn[i],self.flow_dz[i],width=arrowwidth[i],color='k') for i in range(len(self.flow))] a = PatchCollection(flow_arrows, cmap=self.cmapNameFlow, norm=self.flow_norm) a.set_array(self.flow) @@ -811,7 +832,11 @@ def update_data(self, ii): self.flow = flow[mask] # Keep track of the maximum flows for the colorbar self.flow_max_history = np.roll(self.flow_max_history,1) - self.flow_max_history[0] = np.max(self.flow) + try: + ## might raise error if flow is not above threshold + self.flow_max_history[0] = np.max(self.flow) + except: + pass if self.separate_fission: self.handle_fission() @@ -923,6 +948,7 @@ def update_frame(self, ii): self.update_abun_plot() self.update_fission_plot() self.update_flow_plot() + self.update_slider(ii) return ii @@ -937,8 +963,61 @@ def save_frame(self, ii): def get_funcanimation(self, frames=None, **kwargs): if frames is None: frames = range(self.n_timesteps) - return FuncAnimation(self.fig, self.update_frame, + self.frames=frames + self.animation = FuncAnimation(self.fig, self.update_frame, frames=frames, **kwargs) + if self.use_slider: + self.time_slider() + return self.animation + + def time_slider(self): + self.slider_bar = Slider(self.ax_slider, '', 1, self.n_timesteps-1, valinit=1) + self.slider_bar.valtext.set_text('') + self.slider_bar.on_changed(self.on_slider_update) + + def on_slider_update(self, val): + ii = int(self.slider_bar.val) + self.update_frame(ii) + + def update_slider(self, ii): + try: + # avoid infinite recursion if misusing slider as timebar for animation + self.slider_bar.eventson =False + self.slider_bar.set_val(ii) + self.slider_bar.eventson = True + + self.slider_bar.valtext.set_text('') + except: + pass + + def pause_movie(self, click_event): + if click_event.inaxes == self.ax_slider: + self.animation.pause() + pass + elif self.movie_paused: + ii = int(self.slider_bar.val) + new_seq = list(range(ii, self.frames[-1])) + list(range(self.frames[0], ii)) + self.animation._iter_gen = lambda: iter(new_seq) + self.animation.frame_seq = self.animation.new_frame_seq() + self.animation.resume() + self.movie_paused = False + else: + self.animation.pause() + self.movie_paused = True + + def arrow_update(self, event): + ii = int(self.slider_bar.val) + if event.key in ['left', 'down']: + self.animation.pause() + self.movie_paused = True + if ii !=self.slider_bar.valmin: + self.slider_bar.set_val(ii-1) + elif event.key in ['right', 'up']: + self.animation.pause() + self.movie_paused = True + if ii !=self.slider_bar.valmax: + self.slider_bar.set_val(ii+1) + @staticmethod diff --git a/bin/movie_script/winnet_movie.py b/bin/movie_script/winnet_movie.py old mode 100755 new mode 100644 index 44c35646..4f386395 --- a/bin/movie_script/winnet_movie.py +++ b/bin/movie_script/winnet_movie.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # Authors: M. Jacobi, J. Kuske, M. Reichert +# Slider option added by H. Rose. # Movie script inspired by Skynet (J. Lippuner) import sys import os @@ -100,6 +101,8 @@ help="Interval of the movie (larger value equals slower).") p.add_option("--mpirun_path", action="store", dest="mpirun_path", default='', \ help="Path of the mpirun command to use for parallel saving.") +p.add_option("--slider", action="store_true", default=False, \ + help="Whether to display a timeslider to the simulation to jump to specific frames.") p.set_usage(""" Visualize a WinNet simulation. Ensure that at least snapshot_every or h_snapshot_every parameter was enabled in the @@ -116,6 +119,7 @@ run_path = options.rundir kwargs = {} +kwargs['slider'] = options.slider kwargs['timescalerange'] = (1e-12, 1e10) kwargs['trackedrange'] = (1e-8, 1e0) kwargs['energyrange'] = (1e10, 1e20) From ddc12da8f37c2dfae61da41f751cf3274f9cb9eb Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Fri, 8 Nov 2024 16:45:35 +0100 Subject: [PATCH 04/12] (+) Play button for the movie Added a playbutton for the movie. Also spacebar is now playing and stopping it. --- bin/movie_script/src_files/FlowAnimation.py | 65 +++++++++++++++------ 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/bin/movie_script/src_files/FlowAnimation.py b/bin/movie_script/src_files/FlowAnimation.py index bbfe774d..b9412ab8 100644 --- a/bin/movie_script/src_files/FlowAnimation.py +++ b/bin/movie_script/src_files/FlowAnimation.py @@ -9,11 +9,11 @@ from matplotlib.collections import PatchCollection from tqdm import tqdm from matplotlib import cm -from matplotlib.patches import Arrow, FancyBboxPatch +from matplotlib.patches import Arrow, FancyBboxPatch from matplotlib.colors import LogNorm, SymLogNorm from matplotlib.colors import ListedColormap, LinearSegmentedColormap from matplotlib.animation import FuncAnimation -from matplotlib.widgets import Slider +from matplotlib.widgets import Slider, Button from wreader import wreader from h5py import File from nucleus_multiple_class import nucleus_multiple @@ -341,11 +341,9 @@ def init_axes(self): if self.use_slider: self.__init_slider() - + # Start with a running movie self.movie_paused = False - self.fig.canvas.mpl_connect('button_press_event', self.pause_movie) - self.fig.canvas.mpl_connect('key_press_event', self.arrow_update) def __init_nucchart_ax(self): @@ -466,7 +464,14 @@ def __init_logo(self): def __init_slider(self): - self.ax_slider = plt.axes([0.1, 0.1, 0.8, 0.05], facecolor='lightgoldenrodyellow') + self.ax_slider = plt.axes([0.18, 0.08, 0.72, 0.02], facecolor='lightgoldenrodyellow') + self.ax_button = plt.axes([0.18-0.018, 0.08, 0.012, 0.022]) # Adjust `bottom` for centering + + self.play_button = Button(self.ax_button, "❚❚") + self.play_button.label.set_color("red") + self.play_button.label.set_fontsize(8) + self.play_button.on_clicked(self.pause_movie) + self.fig.canvas.mpl_connect('key_press_event', self.arrow_update) def init_data(self): @@ -579,9 +584,9 @@ def init_plot(self): # Create patchcollection of arrows width = (self.flow_maxArrowWidth-self.flow_minArrowWidth)/self.flow_prange with np.errstate(divide='ignore'): - arrowwidth = (np.log10(self.flow)-np.log10(self.flow_min))*width + arrowwidth = (np.log10(self.flow)-np.log10(self.flow_min))*width arrowwidth = np.maximum(arrowwidth, self.flow_minArrowWidth) - + flow_arrows = [Arrow(self.flow_N[i],self.flow_Z[i],self.flow_dn[i],self.flow_dz[i],width=arrowwidth[i],color='k') for i in range(len(self.flow))] a = PatchCollection(flow_arrows, cmap=self.cmapNameFlow, norm=self.flow_norm) @@ -974,6 +979,8 @@ def time_slider(self): self.slider_bar = Slider(self.ax_slider, '', 1, self.n_timesteps-1, valinit=1) self.slider_bar.valtext.set_text('') self.slider_bar.on_changed(self.on_slider_update) + self.fig.canvas.mpl_connect('button_press_event', self.on_slider_click) + self.fig.canvas.mpl_connect('button_release_event', self.on_slider_release) def on_slider_update(self, val): ii = int(self.slider_bar.val) @@ -989,34 +996,56 @@ def update_slider(self, ii): self.slider_bar.valtext.set_text('') except: pass - + def pause_movie(self, click_event): - if click_event.inaxes == self.ax_slider: - self.animation.pause() - pass - elif self.movie_paused: + if self.movie_paused: + # Only resume if the slider is not being dragged ii = int(self.slider_bar.val) new_seq = list(range(ii, self.frames[-1])) + list(range(self.frames[0], ii)) self.animation._iter_gen = lambda: iter(new_seq) self.animation.frame_seq = self.animation.new_frame_seq() self.animation.resume() self.movie_paused = False + self.play_button.label.set_text("❚❚") + self.play_button.label.set_color("red") + # Update the appearance of the button + self.fig.canvas.draw_idle() else: self.animation.pause() self.movie_paused = True - + self.play_button.label.set_text(" ▶") + self.play_button.label.set_color("green") + + # Update the appearance of the button + self.fig.canvas.draw_idle() + + def on_slider_click(self, event): + if event.inaxes == self.ax_slider: + self.animation.pause() # Pause the animation when clicking the slider + self.movie_paused = True + self.play_button.label.set_text(" ▶") + self.play_button.label.set_color("green") + + def on_slider_release(self, event): + # Reset slider_dragging to False when the mouse is released + if event.inaxes == self.ax_slider: + self.on_slider_update(self.slider_bar.val) # Final update after release + def arrow_update(self, event): ii = int(self.slider_bar.val) if event.key in ['left', 'down']: - self.animation.pause() - self.movie_paused = True + self.movie_paused = False + self.pause_movie(event) if ii !=self.slider_bar.valmin: self.slider_bar.set_val(ii-1) elif event.key in ['right', 'up']: - self.animation.pause() - self.movie_paused = True + self.movie_paused = False + self.pause_movie(event) if ii !=self.slider_bar.valmax: self.slider_bar.set_val(ii+1) + elif event.key == " ": + self.pause_movie(event) + # Update the appearance of the button From 5679fb97137793df35f27e57fb1d2b3cf86d8500 Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Fri, 8 Nov 2024 22:44:31 +0100 Subject: [PATCH 05/12] (+) functionality to interactive movie Added switches to show timescales etc with buttons --- bin/movie_script/src_files/FlowAnimation.py | 263 +++++++++++++++++--- 1 file changed, 227 insertions(+), 36 deletions(-) diff --git a/bin/movie_script/src_files/FlowAnimation.py b/bin/movie_script/src_files/FlowAnimation.py index b9412ab8..190e63a4 100644 --- a/bin/movie_script/src_files/FlowAnimation.py +++ b/bin/movie_script/src_files/FlowAnimation.py @@ -299,6 +299,9 @@ def __init__( # Initialize the colorbars self.init_cbars(abun_cbar, flow_cbar) + # Set up the limits of the plot + self.limits_plot = self.ax.get_xlim(), self.ax.get_ylim() + def init_axes(self): """ @@ -344,6 +347,10 @@ def init_axes(self): # Start with a running movie self.movie_paused = False + # Make white behind the colorbars and plots + self.ax.add_patch(patches.Rectangle((0.3, 0.84), 0.8, 0.15, fill=True, color='w', zorder=100, transform=self.fig.transFigure)) + self.ax.add_patch(patches.Rectangle((0.15, 0.745), 0.395, 0.2, fill=True, color='w', zorder=100, transform=self.fig.transFigure)) + def __init_nucchart_ax(self): @@ -472,6 +479,90 @@ def __init_slider(self): self.play_button.label.set_fontsize(8) self.play_button.on_clicked(self.pause_movie) self.fig.canvas.mpl_connect('key_press_event', self.arrow_update) + self.__interactive_ax = None + + # Check which data could be shown + self.available_data = [] + self.toggle_buttons = [] + if self.wreader.check_existence('tracked_nuclei') !=0: + self.available_data.append('tracked_nuclei') + self.toggle_buttons.append(Button(plt.axes([0.18-0.018, 0.05, 0.012, 0.022]), "⚛")) + # Fontsize + self.toggle_buttons[-1].label.set_fontsize(12) + self.toggle_buttons[-1].label.set_color('k') + self.toggle_buttons[-1].on_clicked(self.toggle_button_event) + + if self.wreader.check_existence('timescales') !=0: + self.available_data.append('timescales') + amount_buttons = len(self.toggle_buttons) + self.toggle_buttons.append(Button(plt.axes([0.18-0.018+0.015*amount_buttons, 0.05, 0.012, 0.022]), r"$\tau$")) + self.toggle_buttons[-1].label.set_fontsize(12) + self.toggle_buttons[-1].label.set_color('tab:green') + self.toggle_buttons[-1].on_clicked(self.toggle_button_event) + + if self.wreader.check_existence('energy') !=0: + self.available_data.append('energy') + amount_buttons = len(self.toggle_buttons) + self.toggle_buttons.append(Button(plt.axes([0.18-0.018+0.015*amount_buttons, 0.05, 0.012, 0.022]), "⚡")) + self.toggle_buttons[-1].label.set_fontsize(12) + self.toggle_buttons[-1].label.set_color('tab:orange') + self.toggle_buttons[-1].on_clicked(self.toggle_button_event) + self.active_button = None + + # Add zoom button + self.zoom_button = Button(plt.axes([0.18-0.018+0.015*(len(self.toggle_buttons)+0.2), 0.05, 0.012, 0.022]), "+") + self.zoom_button.label.set_fontsize(12) + self.zoom_button.label.set_color('k') + self.zoom_button.on_clicked(self.zoom_button_event) + self.zoomed = False + + def zoom_button_event(self, event): + self.__toggle_zoom() + if self.zoomed: + self.zoom_button.label.set_text("-") + else: + self.zoom_button.label.set_text("+") + + def toggle_button_event(self, event): + # Find the button that was clicked + for i, button in enumerate(self.toggle_buttons): + if event.inaxes == button.ax: + # Toggle the state: activate this button and deactivate others + if self.active_button != button: + # Unpress the previously active button if there is one + if self.active_button: + for t in ['top','right','bottom','left']: + self.active_button.ax.spines[t].set_color('k') + self.active_button.ax.spines[t].set_linewidth(0.5) + # Set the new active button + # Ändere die Umrandung des Buttons + for t in ['top','right','bottom','left']: + button.ax.spines[t].set_color('red') + button.ax.spines[t].set_linewidth(2) + + self.active_button = button + if not (self.__interactive_ax is None): + self.__interactive_ax.remove() + if self.available_data[i] == 'timescales': + self.__toggle_timescales() + elif self.available_data[i] == 'energy': + self.__toggle_energy() + elif self.available_data[i] == 'tracked_nuclei': + self.__toggle_tracked() + else: + # Unpress the button + for t in ['top','right','bottom','left']: + button.ax.spines[t].set_color('k') + button.ax.spines[t].set_linewidth(0.5) + self.active_button = None + if not (self.__interactive_ax is None): + self.__interactive_ax.remove() + self.__interactive_ax = None + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = False + self.fig.canvas.draw_idle() + return def init_data(self): @@ -488,21 +579,15 @@ def init_data(self): # Set up timescale data if self.plot_timescales: - self.ts_time = self.wreader.tau['time'] - self.ts_data = [ [ self.wreader.tau["tau_"+str(self.timescale_entries[i][j])] - for j in range(len(self.timescale_entries[i]))] for i in range(len(self.timescale_entries)) ] + self.__init_data_timescales(-1) # Set up energy data if self.plot_energy: - self.energy_time = self.wreader.energy['time'] - self.energy_data = [ self.wreader.energy['engen_'+self.energy_entries[i]] for i in range(len(self.energy_entries)) ] + self.__init_data_energy(-1) # Set up tracked nuclei data if self.plot_tracked: - self.tracked_time = self.wreader.tracked_nuclei['time'] - self.track_nuclei_data = [ self.wreader.tracked_nuclei[n] for n in self.wreader.tracked_nuclei['names'] ] - self.track_nuclei_labels= self.wreader.tracked_nuclei['latex_names'] - + self.__init_data_tracked(-1) # Set up mainout data if self.plot_mainout: @@ -683,25 +768,13 @@ def init_plot(self): # Plot the timescales if self.plot_timescales: - ls = ["-","--"] - self.ts_plot = [ [ self.axTimescales.plot(self.ts_time,self.ts_data[i][j], color=self.timescale_colors[i], ls=ls[j], - label=(self.timescale_labels[i] if (j == 0) else "")) for j in range(len(self.ts_data[i]))] for i in range(len(self.ts_data))] - # Also make the background of the box non-transparent - self.axTimescales.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) - + self.__init_plot_timescales() # Plot the energy if self.plot_energy: - self.energy_plot = [self.axEnergy.plot(self.energy_time,self.energy_data[i], color=self.energy_colors[i], - label=self.energy_labels[i], lw=self.energy_lw[i]) for i in range(len(self.energy_data))] - # Also make the background of the box non-transparent - self.axEnergy.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) - + self.__init_plot_energy() # Plot the tracked nuclei if self.plot_tracked: - self.tracked_plot = [self.axTracked.plot(self.tracked_time,self.track_nuclei_data[i], - label=self.track_nuclei_labels[i]) for i in range(len(self.track_nuclei_labels))] - # Also make the background of the box non-transparent - self.axTracked.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) + self.__init_plot_tracked() # Plot magic numbers if self.plot_magic: @@ -737,6 +810,28 @@ def init_plot(self): self.ax.text(0.9, 0.67, 'Fission products', transform=self.ax.transAxes, fontsize=8, verticalalignment='bottom', horizontalalignment='left') + + def __init_plot_timescales(self): + ls = ["-","--"] + self.ts_plot = [ [ self.axTimescales.plot(self.ts_time,self.ts_data[i][j], color=self.timescale_colors[i], ls=ls[j], + label=(self.timescale_labels[i] if (j == 0) else "")) for j in range(len(self.ts_data[i]))] for i in range(len(self.ts_data))] + # Also make the background of the box non-transparent + self.axTimescales.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) + + def __init_plot_energy(self): + self.energy_plot = [self.axEnergy.plot(self.energy_time,self.energy_data[i], color=self.energy_colors[i], + label=self.energy_labels[i], lw=self.energy_lw[i]) for i in range(len(self.energy_data))] + # Also make the background of the box non-transparent + self.axEnergy.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) + + def __init_plot_tracked(self): + self.tracked_plot = [self.axTracked.plot(self.tracked_time,self.track_nuclei_data[i], + label=self.track_nuclei_labels[i]) for i in range(len(self.track_nuclei_labels))] + # Also make the background of the box non-transparent + self.axTracked.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) + + + def init_cbars(self, abun_cbar, flow_cbar): """ Initialize the colorbars. @@ -795,20 +890,13 @@ def update_data(self, ii): if self.plot_timescales: - self.ts_time = self.wreader.tau['time'][:ii] - self.ts_time = self.ts_time-self.ts_time[0] - self.ts_data = [ [ self.wreader.tau['tau_'+str(self.timescale_entries[i][j])][:ii] - for j in range(len(self.timescale_entries[i]))] for i in range(len(self.timescale_entries)) ] + self.__init_data_timescales(ii) if self.plot_energy: - self.energy_time = self.wreader.energy['time'][:ii] - self.energy_time = self.energy_time-self.energy_time[0] - self.energy_data = [ self.wreader.energy['engen_'+self.energy_entries[i]][:ii] for i in range(len(self.energy_entries)) ] + self.__init_data_energy(ii) if self.plot_tracked: - self.tracked_time = self.wreader.tracked_nuclei['time'][:ii] - self.tracked_time = self.tracked_time-self.tracked_time[0] - self.track_nuclei_data = [ self.wreader.tracked_nuclei[n][:ii] for n in self.wreader.tracked_nuclei['names'] ] + self.__init_data_tracked(ii) if self.plot_mainout: self.mainout_time = self.wreader.mainout['time'][:ii] @@ -845,7 +933,23 @@ def update_data(self, ii): if self.separate_fission: self.handle_fission() - + def __init_data_timescales(self, ii): + self.ts_time = self.wreader.tau['time'][:ii] + self.ts_time = self.ts_time-self.ts_time[0] + self.ts_data = [ [ self.wreader.tau['tau_'+str(self.timescale_entries[i][j])][:ii] + for j in range(len(self.timescale_entries[i]))] for i in range(len(self.timescale_entries)) ] + + def __init_data_energy(self, ii): + self.energy_time = self.wreader.energy['time'][:ii] + self.energy_time = self.energy_time-self.energy_time[0] + self.energy_data = [ self.wreader.energy['engen_'+self.energy_entries[i]][:ii] for i in range(len(self.energy_entries)) ] + + def __init_data_tracked(self, ii, force_label_init=False): + self.tracked_time = self.wreader.tracked_nuclei['time'][:ii] + self.tracked_time = self.tracked_time-self.tracked_time[0] + self.track_nuclei_data = [ self.wreader.tracked_nuclei[n][:ii] for n in self.wreader.tracked_nuclei['names'] ] + if ii == -1 or force_label_init: + self.track_nuclei_labels= self.wreader.tracked_nuclei['latex_names'] def handle_fission(self,): """ @@ -1045,7 +1149,94 @@ def arrow_update(self, event): self.slider_bar.set_val(ii+1) elif event.key == " ": self.pause_movie(event) - # Update the appearance of the button + elif ((event.key == "t") or (event.key == "e") + or (event.key == 'n') or (event.key == 'd')): + + if not (self.__interactive_ax is None): + self.__interactive_ax.remove() + + if event.key == "t": + # Toggle timescales + self.__toggle_timescales() + elif event.key == 'e': + # Toggle energy + self.__toggle_energy() + elif event.key == 'n': + # Toggle tracked nuclei + self.__toggle_tracked() + elif event.key == "d": + # Shut of additional plots + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = False + self.__interactive_ax = None + + self.fig.canvas.draw_idle() + + def __toggle_timescales(self): + if 'timescales' in self.available_data: + # Toggle timescales + if self.plot_timescales == True: + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = False + self.__interactive_ax = None + else: + ii = int(self.slider_bar.val) + self.__init_data_timescales(ii) + self.__init_axTimescales() + self.plot_timescales = True + self.plot_energy = False + self.plot_tracked = False + self.__init_plot_timescales() + self.__interactive_ax = self.axTimescales + + def __toggle_energy(self): + if 'energy' in self.available_data: + # Toggle energy + if self.plot_energy == True: + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = False + self.__interactive_ax = None + else: + ii = int(self.slider_bar.val) + self.__init_data_energy(ii) + self.__init_axEnergy() + self.plot_timescales = False + self.plot_energy = True + self.plot_tracked = False + self.__init_plot_energy() + self.__interactive_ax = self.axEnergy + + def __toggle_tracked(self): + if 'tracked_nuclei' in self.available_data: + # Toggle energy + if self.plot_tracked == True: + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = False + self.__interactive_ax = None + else: + ii = int(self.slider_bar.val) + self.__init_data_tracked(ii, force_label_init=True) + self.__init_axTracked() + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = True + self.__init_plot_tracked() + self.__interactive_ax = self.axTracked + + def __toggle_zoom(self): + if self.zoomed: + self.ax.set_xlim(self.limits_plot[0]) + self.ax.set_ylim(self.limits_plot[1]) + self.zoomed = False + else: + self.ax.set_xlim(-5.5, 100) + self.ax.set_ylim(-6.5, 50) + self.zoomed = True + self.fig.canvas.draw_idle() From 6446dd8e386f6fb58fde0ed0b9c1c186befa6455 Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Mon, 11 Nov 2024 12:06:15 +0100 Subject: [PATCH 06/12] (*) More powerful interactive mode of movie Added several things to the interactive mode of the movie. Further, renamed the option to --interactive instead of --slider. --- bin/movie_script/README.md | 10 +- bin/movie_script/src_files/FlowAnimation.py | 272 +++++++++++++++++-- bin/movie_script/src_files/template_class.py | 86 ++++++ bin/movie_script/src_files/wreader.py | 14 +- bin/movie_script/winnet_movie.py | 25 +- 5 files changed, 382 insertions(+), 25 deletions(-) create mode 100644 bin/movie_script/src_files/template_class.py diff --git a/bin/movie_script/README.md b/bin/movie_script/README.md index 60584211..03ed5fc7 100644 --- a/bin/movie_script/README.md +++ b/bin/movie_script/README.md @@ -93,6 +93,12 @@ This command will display the movie in a separate window. Note that this process - `--tracked_max=TRACKED_MAX` Upper limit of the tracked nuclei mass fractions. +- `--amainout_min=AMAINOUT_MIN` + Lower limit of the additional mainout abundances. + +- `--amainout_max=AMAINOUT_MAX` + Upper limit of the additional mainout abundances. + - `--time_min=T_MIN` Lower limit of the time. @@ -126,8 +132,8 @@ This command will display the movie in a separate window. Note that this process - `--frame_max=FRAME_MAX` Value of the last frame (default: end of the simulation). -- `--slider` - Whether or not to add a slider to the simulation to jump to specific frames. +- `--interactive` + Whether to show the movie in interactive mode. - `--save` Whether or not saving the movie. diff --git a/bin/movie_script/src_files/FlowAnimation.py b/bin/movie_script/src_files/FlowAnimation.py index 190e63a4..293ca320 100644 --- a/bin/movie_script/src_files/FlowAnimation.py +++ b/bin/movie_script/src_files/FlowAnimation.py @@ -64,6 +64,8 @@ def __init__( additional_plot = 'none', # Tracked nuclei trackedrange = (1e-8, 1), + # Additional mainout + amainoutrange = (5e-10, 1), # Energy energyrange = (1e10, 1e20), # Timescales @@ -75,7 +77,7 @@ def __init__( temperaturerange = (0, 10), yerange = (0.0, 0.55), plot_logo = True, - slider = False + interactive = False ): """ Parameters @@ -138,6 +140,8 @@ def __init__( Additional plot to be made, possible values: 'None', 'timescales', 'energy', 'tracked'. trackedrange : tuple Range of the tracked nuclei plot. + amainoutrange : tuple + Range of the additional mainout plot. energyrange : tuple Range of the energy axis. timescalerange : tuple @@ -154,8 +158,8 @@ def __init__( Range of the electron fraction axis in the mainout plot. plot_logo : bool Plot the WinNet logo. - slider : bool - Use a slider widget. + interactive : bool + Enable interactive mode. """ @@ -198,7 +202,7 @@ def __init__( self.timerange = timerange # Range of the time axis self.plot_mainout = plot_mainout # Plot the mainout data self.plot_logo = plot_logo # Plot the WinNet logo - self.use_slider = slider # Use slider widget + self.interactive = interactive # Have it interactive self.flow_group = 'flows' # Name of the group in the HDF5 file that contains the flow data self.plot_flow = plot_flow # Plot the flow of the abundances self.fig = fig # Figure to plot the animation on @@ -212,10 +216,11 @@ def __init__( self.flow_adapt_width = flow_adapt_width # Adapt the width of the flow arrows to the flow self.flow_maxArrowWidth = flow_maxArrowWidth # Maximum width of the flow arrows self.flow_minArrowWidth = flow_minArrowWidth # Minimum width of the flow arrows - self.flow_min = flow_min # Minimum value for the flow - self.flow_max = flow_max # Maximum value for the flow - self.flow_adapt_prange = flow_adapt_prange # Adapt the color range of the flow to the data - self.fission_minflow = fission_minflow # Minimum value for the fission flow + self.flow_min = flow_min # Minimum value for the flow + self.flow_max = flow_max # Maximum value for the flow + self.flow_adapt_prange = flow_adapt_prange # Adapt the color range of the flow to the data + self.fission_minflow = fission_minflow # Minimum value for the fission flow + self.amainoutrange = amainoutrange # Range of the additional mainout plot if (self.flow_adapt_prange): self.flow_prange = flow_prange # Range (in log10) of the flow @@ -227,18 +232,27 @@ def __init__( self.plot_timescales = True self.plot_energy = False self.plot_tracked = False + self.plot_addmainout = False elif self.additional_plot == 'energy': self.plot_timescales = False self.plot_energy = True self.plot_tracked = False + self.plot_addmainout = False elif self.additional_plot == 'tracked': self.plot_timescales = False self.plot_energy = False self.plot_tracked = True + self.plot_addmainout = False + elif self.additional_plot == 'mainout': + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = False + self.plot_addmainout = True elif self.additional_plot == 'none': self.plot_timescales = False self.plot_energy = False self.plot_tracked = False + self.plot_addmainout = False else: raise ValueError(f"Additional plot {self.additional_plot} not recognized. Possible values: 'None', 'timescales', 'energy', 'tracked'") @@ -302,6 +316,70 @@ def __init__( # Set up the limits of the plot self.limits_plot = self.ax.get_xlim(), self.ax.get_ylim() + # For interactive flow range + self.flow_max_offset = 0.0 + self.flow_min_offset = 0.0 + + if self.interactive: + self.__init_sunet_indicator() + + + def __init_sunet_indicator(self): + sunet_path = self.wreader.template['net_source'] + nuclei_names = np.loadtxt(sunet_path,dtype=str) + nm = nucleus_multiple(names=nuclei_names) + + self.__sunet_lines = [] + # Loop through Zs + for Z in np.unique(nm.Z): + # Find the Ns that are have larger than distance one to the next N + mask = (nm.Z == Z) + Ns = nm.N[mask] + # Find the Ns that are have larger than distance one to the next N + diff = np.diff(Ns) + mask = np.where(diff > 1)[0] + # Loop through the Ns + # Plot left and right + line = self.ax.plot([np.min(Ns)-0.5, np.min(Ns)-0.5], [Z-0.5, Z+0.5], color='red', zorder=1000, lw=1) + self.__sunet_lines.append(line) + line = self.ax.plot([np.max(Ns)+0.5, np.max(Ns)+0.5], [Z-0.5, Z+0.5], color='red', zorder=1000, lw=1) + self.__sunet_lines.append(line) + for i in mask: + # Add a line to the plot + line = self.ax.plot([Ns[i]+0.5, Ns[i]+0.5], [Z-0.5, Z+0.5], color='red', zorder=1000, lw=1) + self.__sunet_lines.append(line) + line = self.ax.plot([Ns[i+1]-0.5, Ns[i+1]-0.5], [Z-0.5, Z+0.5], color='red', zorder=1000, lw=1) + self.__sunet_lines.append(line) + + # Same but for Z + for N in np.unique(nm.N): + # Find the Ns that are have larger than distance one to the next N + mask = (nm.N == N) + Zs = nm.Z[mask] + # Find the Ns that are have larger than distance one to the next N + diff = np.diff(Zs) + mask = np.where(diff > 1)[0] + # Loop through the Ns + # Plot left and right + line = self.ax.plot([N-0.5, N+0.5], [np.min(Zs)-0.5, np.min(Zs)-0.5], color='red', zorder=1000, lw=1) + self.__sunet_lines.append(line) + line = self.ax.plot([N-0.5, N+0.5], [np.max(Zs)+0.5, np.max(Zs)+0.5], color='red', zorder=1000, lw=1) + self.__sunet_lines.append(line) + for i in mask: + # Add a line to the plot + line = self.ax.plot([N-0.5, N+0.5], [Zs[i]+0.5, Zs[i]+0.5], color='red', zorder=1000, lw=1) + self.__sunet_lines.append(line) + line = self.ax.plot([N-0.5, N+0.5], [Zs[i+1]-0.5, Zs[i+1]-0.5], color='red', zorder=1000, lw=1) + self.__sunet_lines.append(line) + + # Hide the lines + for line in self.__sunet_lines: + for l in line: + l.set_visible(False) + self.sunet_indication = False + + + def init_axes(self): """ @@ -336,13 +414,17 @@ def init_axes(self): if self.plot_tracked: self.__init_axTracked() + # Additional mainout + if self.plot_addmainout: + self.__init_axAddMainout() + # WinNet logo if self.plot_logo: self.__init_logo() - # Slider - if self.use_slider: - self.__init_slider() + # interactive stuff + if self.interactive: + self.__init_interactive() # Start with a running movie self.movie_paused = False @@ -421,6 +503,18 @@ def __init_axEnergy(self): self.axEnergy.set_xscale('log') self.axEnergy.set_xlim(self.timerange[0],self.timerange[1]) + def __init_axAddMainout(self): + """ + Initialize the axes and everything figure related of the energy plot. + """ + self.axAddMainout = plt.axes([0.15,0.55,0.20,0.17]) + self.axAddMainout.set_xlabel('Time [s]') + self.axAddMainout.set_ylabel('Abundance') + self.axAddMainout.set_yscale('log') + self.axAddMainout.set_ylim(self.amainoutrange[0],self.amainoutrange[1]) + self.axAddMainout.set_xscale('log') + self.axAddMainout.set_xlim(self.timerange[0],self.timerange[1]) + def __init_axMainout(self): """ Initialize the axes and everything figure related of the mainout plot. @@ -469,7 +563,7 @@ def __init_logo(self): self.axLogo.axis('off') self.axLogo.imshow(plt.imread(os.path.join(self.__data_path,'WinNet_logo.png'))) - def __init_slider(self): + def __init_interactive(self): self.ax_slider = plt.axes([0.18, 0.08, 0.72, 0.02], facecolor='lightgoldenrodyellow') self.ax_button = plt.axes([0.18-0.018, 0.08, 0.012, 0.022]) # Adjust `bottom` for centering @@ -481,6 +575,11 @@ def __init_slider(self): self.fig.canvas.mpl_connect('key_press_event', self.arrow_update) self.__interactive_ax = None + # Add a bookmark at a certain time in the slider + # Calculate neutron freeze-out time + # nfreezeout = np.argmin(abs(1-self.wreader.mainout['yn']/self.wreader.mainout['yheavy'])) + # self.ax_slider.axvline(nfreezeout, color='red', linestyle='--', linewidth=1) # Bookmark indicators + # Check which data could be shown self.available_data = [] self.toggle_buttons = [] @@ -507,6 +606,14 @@ def __init_slider(self): self.toggle_buttons[-1].label.set_fontsize(12) self.toggle_buttons[-1].label.set_color('tab:orange') self.toggle_buttons[-1].on_clicked(self.toggle_button_event) + + if self.wreader.check_existence('mainout') !=0: + self.available_data.append('mainout') + amount_buttons = len(self.toggle_buttons) + self.toggle_buttons.append(Button(plt.axes([0.18-0.018+0.015*amount_buttons, 0.05, 0.012, 0.022]), "m")) + self.toggle_buttons[-1].label.set_fontsize(12) + self.toggle_buttons[-1].label.set_color('tab:blue') + self.toggle_buttons[-1].on_clicked(self.toggle_button_event) self.active_button = None # Add zoom button @@ -516,6 +623,74 @@ def __init_slider(self): self.zoom_button.on_clicked(self.zoom_button_event) self.zoomed = False + # Check if the sunet path exists + supath = self.wreader.template['net_source'] + if os.path.exists(supath): + self.sunet_button = Button(plt.axes([0.18-0.018+0.015*(len(self.toggle_buttons)+1+0.2), 0.05, 0.012, 0.022]), "S") + self.sunet_button.label.set_fontsize(12) + self.sunet_button.label.set_color('k') + self.sunet_button.on_clicked(self.sunet_button_event) + + # Check if flow is plotted and add a button to change the flow range + if self.plot_flow: + self.flow_buttons = [Button(plt.axes([0.88, 0.905, 0.01, 0.02]), "+")] + self.flow_buttons[-1].label.set_fontsize(12) + self.flow_buttons[-1].label.set_color('k') + self.flow_buttons[-1].on_clicked(self.flow_button_event) + + self.flow_buttons.append(Button(plt.axes([0.868, 0.905, 0.01, 0.02]), "-")) + self.flow_buttons[-1].label.set_fontsize(12) + self.flow_buttons[-1].label.set_color('k') + self.flow_buttons[-1].on_clicked(self.flow_button_event) + + self.flow_buttons.append(Button(plt.axes([0.762, 0.905, 0.01, 0.02]),"+")) + self.flow_buttons[-1].label.set_fontsize(12) + self.flow_buttons[-1].label.set_color('k') + self.flow_buttons[-1].on_clicked(self.flow_button_event) + + self.flow_buttons.append(Button(plt.axes([0.75, 0.905, 0.01, 0.02]), "-")) + self.flow_buttons[-1].label.set_fontsize(12) + self.flow_buttons[-1].label.set_color('k') + self.flow_buttons[-1].on_clicked(self.flow_button_event) + + # Add a button to change reset + self.flow_buttons.append(Button(plt.axes([0.775, 0.905, 0.01, 0.02]), "⟲")) + self.flow_buttons[-1].label.set_fontsize(12) + self.flow_buttons[-1].label.set_color('k') + self.flow_buttons[-1].on_clicked(self.flow_button_event) + + # self.flow_button.on_clicked(self.flow_button_event) + + def flow_button_event(self, event): + if event.inaxes == self.flow_buttons[0].ax: + self.flow_max_offset += 0.5 + elif event.inaxes == self.flow_buttons[1].ax: + self.flow_max_offset -= 0.5 + elif event.inaxes == self.flow_buttons[2].ax: + self.flow_min_offset -= 0.5 + elif event.inaxes == self.flow_buttons[3].ax: + self.flow_min_offset += 0.5 + elif event.inaxes == self.flow_buttons[4].ax: + self.flow_max_offset = 0.0 + self.flow_min_offset = 0.0 + + # Refresh the animation at current position + if self.movie_paused: + self.update_frame(self.slider_bar.val) + + self.fig.canvas.draw_idle() + pass + + + + def sunet_button_event(self, event): + self.sunet_indication = not self.sunet_indication + for line in self.__sunet_lines: + for l in line: + l.set_visible(self.sunet_indication) + self.fig.canvas.draw_idle() + + def zoom_button_event(self, event): self.__toggle_zoom() if self.zoomed: @@ -549,6 +724,8 @@ def toggle_button_event(self, event): self.__toggle_energy() elif self.available_data[i] == 'tracked_nuclei': self.__toggle_tracked() + elif self.available_data[i] == 'mainout': + self.__toggle_addmainout() else: # Unpress the button for t in ['top','right','bottom','left']: @@ -561,6 +738,7 @@ def toggle_button_event(self, event): self.plot_timescales = False self.plot_energy = False self.plot_tracked = False + self.plot_addmainout = False self.fig.canvas.draw_idle() return @@ -589,6 +767,10 @@ def init_data(self): if self.plot_tracked: self.__init_data_tracked(-1) + # Set up additional mainout data + if self.plot_addmainout: + self.__init_data_addmainout(-1) + # Set up mainout data if self.plot_mainout: self.mainout_time = self.wreader.mainout['time'] @@ -772,6 +954,9 @@ def init_plot(self): # Plot the energy if self.plot_energy: self.__init_plot_energy() + # Plot the additional mainout + if self.plot_addmainout: + self.__init_plot_addmainout() # Plot the tracked nuclei if self.plot_tracked: self.__init_plot_tracked() @@ -818,6 +1003,13 @@ def __init_plot_timescales(self): # Also make the background of the box non-transparent self.axTimescales.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) + def __init_plot_addmainout(self): + self.addmainout_plot = [self.axAddMainout.plot(self.addmainout_time,self.addmainout_data[k], + label=self.addmainout_label[i], lw=1) for i,k in enumerate(self.addmainout_data.keys())] + # Also make the background of the box non-transparent + self.axAddMainout.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) + + def __init_plot_energy(self): self.energy_plot = [self.axEnergy.plot(self.energy_time,self.energy_data[i], color=self.energy_colors[i], label=self.energy_labels[i], lw=self.energy_lw[i]) for i in range(len(self.energy_data))] @@ -895,6 +1087,9 @@ def update_data(self, ii): if self.plot_energy: self.__init_data_energy(ii) + if self.plot_addmainout: + self.__init_data_addmainout(ii) + if self.plot_tracked: self.__init_data_tracked(ii) @@ -939,6 +1134,17 @@ def __init_data_timescales(self, ii): self.ts_data = [ [ self.wreader.tau['tau_'+str(self.timescale_entries[i][j])][:ii] for j in range(len(self.timescale_entries[i]))] for i in range(len(self.timescale_entries)) ] + def __init_data_addmainout(self, ii): + self.addmainout_time = self.wreader.mainout['time'][:ii] + self.addmainout_time = self.addmainout_time-self.addmainout_time[0] + self.addmainout_label = [r"Y$_n$", r"Y$_p$", r"Y$_\alpha$", r"Y$_{\text{heavy}}$", r"Y$_{\text{light}}$"] + self.addmainout_data = {} + self.addmainout_data["yn"] = self.wreader.mainout['yn'][:ii] + self.addmainout_data["yp"] = self.wreader.mainout['yp'][:ii] + self.addmainout_data["ya"] = self.wreader.mainout['ya'][:ii] + self.addmainout_data["yheavy"] = self.wreader.mainout['yheavy'][:ii] + self.addmainout_data["ylight"] = self.wreader.mainout['ylight'][:ii] + def __init_data_energy(self, ii): self.energy_time = self.wreader.energy['time'][:ii] self.energy_time = self.energy_time-self.energy_time[0] @@ -991,6 +1197,9 @@ def update_abun_plot(self,): if self.plot_energy: [ self.energy_plot[i][0].set_xdata(self.energy_time) for i in range(len(self.energy_plot))] [ self.energy_plot[i][0].set_ydata(self.energy_data[i]) for i in range(len(self.energy_plot))] + if self.plot_addmainout: + [ self.addmainout_plot[i][0].set_xdata(self.addmainout_time) for i in range(len(self.addmainout_plot))] + [ self.addmainout_plot[i][0].set_ydata(self.addmainout_data[k]) for i,k in enumerate(self.addmainout_data.keys())] if self.plot_tracked: [ self.tracked_plot[i][0].set_xdata(self.tracked_time) for i in range(len(self.tracked_plot))] [ self.tracked_plot[i][0].set_ydata(self.track_nuclei_data[i]) for i in range(len(self.tracked_plot))] @@ -1027,8 +1236,8 @@ def update_flow_plot(self,): if self.plot_flow: # Adapt flowmin and flowmax if self.flow_adapt_prange: - lmaxflow = (np.nanmean(np.log10(self.flow_max_history)))+0.5 - lminflow = lmaxflow-self.flow_prange + lmaxflow = (np.nanmean(np.log10(self.flow_max_history)))+0.5+self.flow_max_offset + lminflow = lmaxflow-self.flow_prange-self.flow_min_offset self.flow_cbar.mappable.set_clim(vmin=10**lminflow, vmax=10**lmaxflow) self.flow_max = 10**lmaxflow self.flow_min = 10**lminflow @@ -1075,7 +1284,7 @@ def get_funcanimation(self, frames=None, **kwargs): self.frames=frames self.animation = FuncAnimation(self.fig, self.update_frame, frames=frames, **kwargs) - if self.use_slider: + if self.interactive: self.time_slider() return self.animation @@ -1150,7 +1359,8 @@ def arrow_update(self, event): elif event.key == " ": self.pause_movie(event) elif ((event.key == "t") or (event.key == "e") - or (event.key == 'n') or (event.key == 'd')): + or (event.key == 'n') or (event.key == 'd') + or (event.key == 'm')): if not (self.__interactive_ax is None): self.__interactive_ax.remove() @@ -1164,11 +1374,15 @@ def arrow_update(self, event): elif event.key == 'n': # Toggle tracked nuclei self.__toggle_tracked() + elif event.key == 'm': + # Toggle tracked nuclei + self.__toggle_addmainout() elif event.key == "d": # Shut of additional plots self.plot_timescales = False self.plot_energy = False self.plot_tracked = False + self.plot_addmainout = False self.__interactive_ax = None self.fig.canvas.draw_idle() @@ -1180,6 +1394,7 @@ def __toggle_timescales(self): self.plot_timescales = False self.plot_energy = False self.plot_tracked = False + self.plot_addmainout = False self.__interactive_ax = None else: ii = int(self.slider_bar.val) @@ -1188,6 +1403,7 @@ def __toggle_timescales(self): self.plot_timescales = True self.plot_energy = False self.plot_tracked = False + self.plot_addmainout = False self.__init_plot_timescales() self.__interactive_ax = self.axTimescales @@ -1198,6 +1414,7 @@ def __toggle_energy(self): self.plot_timescales = False self.plot_energy = False self.plot_tracked = False + self.plot_addmainout = False self.__interactive_ax = None else: ii = int(self.slider_bar.val) @@ -1206,9 +1423,30 @@ def __toggle_energy(self): self.plot_timescales = False self.plot_energy = True self.plot_tracked = False + self.plot_addmainout = False self.__init_plot_energy() self.__interactive_ax = self.axEnergy + def __toggle_addmainout(self): + if 'mainout' in self.available_data: + # Toggle additional mainout information + if self.plot_addmainout == True: + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = False + self.plot_addmainout = False + self.__interactive_ax = None + else: + ii = int(self.slider_bar.val) + self.__init_data_addmainout(ii) + self.__init_axAddMainout() + self.plot_timescales = False + self.plot_energy = False + self.plot_tracked = False + self.plot_addmainout = True + self.__init_plot_addmainout() + self.__interactive_ax = self.axAddMainout + def __toggle_tracked(self): if 'tracked_nuclei' in self.available_data: # Toggle energy @@ -1216,6 +1454,7 @@ def __toggle_tracked(self): self.plot_timescales = False self.plot_energy = False self.plot_tracked = False + self.plot_addmainout = False self.__interactive_ax = None else: ii = int(self.slider_bar.val) @@ -1224,6 +1463,7 @@ def __toggle_tracked(self): self.plot_timescales = False self.plot_energy = False self.plot_tracked = True + self.plot_addmainout = False self.__init_plot_tracked() self.__interactive_ax = self.axTracked diff --git a/bin/movie_script/src_files/template_class.py b/bin/movie_script/src_files/template_class.py new file mode 100644 index 00000000..556fc02d --- /dev/null +++ b/bin/movie_script/src_files/template_class.py @@ -0,0 +1,86 @@ +# Author: Moritz Reichert +# Date : 26.09.2024 +import numpy as np + + +class template(object): + """ + Class to read a WinNet template file. + """ + + def __init__(self, path): + """ + Initialize the template class. + """ + self.path = path + + + def read_data(self): + """ + Read the data from the template file and store it in a dictionary. + """ + # Create an empty dictionary to store the entries + self.__entries = {} + + # Read the data from the file + with open(self.path, 'r') as f: + self.data = f.readlines() + for line in self.data: + if line.strip().startswith('#'): + continue + + if line.strip() =="": + continue + + key = line.split("=")[0].strip() + value = line.split("=")[1].strip().replace('"', '').replace("'", "") + self.__entries[key] = value + + @property + def entries(self): + """ + Get the entries of the template file. + """ + # Check if entry exists. + #print all attributes of the object + if not hasattr(self, '_template__entries'): + self.read_data() + return self.__entries + + + def __getitem__(self, key): + """ + Get the value of a specific key. + """ + if not hasattr(self, '_template__entries'): + self.read_data() + return self.entries[key] + + def __setitem__(self, key, value): + """ + Set the value of a specific key. + """ + if not hasattr(self, '_template__entries'): + self.read_data() + self.__entries[key] = value + + + def save_template(self, path, winnet_path=None): + """ + Save the template file. + """ + with open(path, 'w') as f: + for key, value in self.entries.items(): + if winnet_path is None: + f.write(f"{key} = {value}\n") + else: + entry = str(value).replace("@WINNET@",winnet_path) + f.write(f"{key} = {entry}\n") + + +if __name__ == '__main__': + # Example: + path = '/home/mreichert/data/Networks/comparison_winNet/WinNet-dev/par/NSE_comp.par' + t = template(path) + print(t["isotopes_file"]) + t.save_template('test.par', winnet_path='../../runs/winnet') \ No newline at end of file diff --git a/bin/movie_script/src_files/wreader.py b/bin/movie_script/src_files/wreader.py index 66d45982..4bfb82e9 100644 --- a/bin/movie_script/src_files/wreader.py +++ b/bin/movie_script/src_files/wreader.py @@ -3,6 +3,7 @@ import numpy as np from tqdm import tqdm from nucleus_multiple_class import nucleus_multiple +from template_class import template import h5py import os @@ -30,6 +31,18 @@ def __init__(self, path, silent=False): # The path to the snapshots in case of ascii mode self.__snapshot_path = os.path.join(path, "snaps") + # Path to template file. Find file with .par ending + self.__template_path = os.path.join(path, [f for f in os.listdir(path) if f.endswith('.par')][0]) + + + @property + def template(self): + """ + Check if the run has crashed + """ + if not hasattr(self,"_wreader__template"): + self.__template = template(self.__template_path) + return self.__template @property @@ -54,7 +67,6 @@ def __read_is_crashed(self): self.__is_crashed = False - @property def A(self): """ diff --git a/bin/movie_script/winnet_movie.py b/bin/movie_script/winnet_movie.py index 4f386395..c85ad2bd 100644 --- a/bin/movie_script/winnet_movie.py +++ b/bin/movie_script/winnet_movie.py @@ -51,8 +51,9 @@ help="Whether or not disabling the indication for the magic number.") p.add_option("--additional_plot", action="store", dest="additional_plot", default="", \ help="Whether or not to show an additional plot in the top left corner. "+ - "Possible options are 'timescales', 'tracked', or 'energy'"+ - " for plotting average timescales, mass fractions of tracked nuclei, or nuclear energy generation.") + "Possible options are 'timescales', 'tracked', 'mainout', or 'energy'"+ + " for plotting average timescales, mass fractions of tracked nuclei, additional mainout data"+ + ", or nuclear energy generation.") p.add_option("--tau_min", action="store", dest="tau_min", default="", \ help="Lower limit of the average timescales.") p.add_option("--tau_max", action="store", dest="tau_max", default="", \ @@ -64,7 +65,11 @@ p.add_option("--tracked_min", action="store", dest="tracked_min", default="", \ help="Lower limit of the tracked nuclei mass fractions.") p.add_option("--tracked_max", action="store", dest="tracked_max", default="", \ - help="Upper limit of the tracked nuclei mass fractions.") + help="Upper limit of the tracked nuclei mass fractions.") +p.add_option("--amainout_min", action="store", dest="amainout_min", default="", \ + help="Lower limit of the additional mainout abundances.") +p.add_option("--amainout_max", action="store", dest="amainout_max", default="", \ + help="Upper limit of the additional mainout abundances.") p.add_option("--time_min", action="store", dest="t_min", default="", \ help="Lower limit of the time.") p.add_option("--time_max", action="store", dest="t_max", default="", \ @@ -101,8 +106,8 @@ help="Interval of the movie (larger value equals slower).") p.add_option("--mpirun_path", action="store", dest="mpirun_path", default='', \ help="Path of the mpirun command to use for parallel saving.") -p.add_option("--slider", action="store_true", default=False, \ - help="Whether to display a timeslider to the simulation to jump to specific frames.") +p.add_option("--interactive", action="store_true", default=False, \ + help="Whether to show the movie in interactive mode.") p.set_usage(""" Visualize a WinNet simulation. Ensure that at least snapshot_every or h_snapshot_every parameter was enabled in the @@ -119,7 +124,7 @@ run_path = options.rundir kwargs = {} -kwargs['slider'] = options.slider +kwargs['interactive'] = options.interactive kwargs['timescalerange'] = (1e-12, 1e10) kwargs['trackedrange'] = (1e-8, 1e0) kwargs['energyrange'] = (1e10, 1e20) @@ -127,6 +132,7 @@ kwargs['densityrange'] = (1e-5, 1e12) kwargs['temperaturerange'] = (0, 10) kwargs['yerange'] = (0.0, 0.55) +kwargs['amainoutrange'] = (5e-10,1e0) if options.flow_min: kwargs['flow_min'] = float(options.flow_min) if options.flow_max: kwargs['flow_max'] = float(options.flow_max) @@ -159,6 +165,8 @@ if options.temperature_max: kwargs['temperaturerange'] = (kwargs['temperaturerange'][0], float(options.temperature_max)) if options.ye_min: kwargs['yerange'] = (float(options.ye_min), kwargs['yerange'][1]) if options.ye_max: kwargs['yerange'] = (kwargs['yerange'][0], float(options.ye_max)) +if options.amainout_min: kwargs['amainoutrange'] = (float(options.amainout_min), kwargs['amainoutrange'][1]) +if options.amainout_max: kwargs['amainoutrange'] = (kwargs['amainoutrange'][0], float(options.amainout_max)) @@ -187,6 +195,11 @@ if value == 0: print('No tracked nuclei found. Disabling tracked nuclei. Remove --additional_plot to disable this message.') kwargs['additional_plot'] = 'none' + elif options.additional_plot == 'mainout': + value = w.check_existence('mainout') + if value == 0: + print('No mainout found. Disabling mainout. Remove --additional_plot to disable this message.') + kwargs['additional_plot'] = 'none' if not options.disable_mainout: value = w.check_existence('mainout') if value == 0: From d2b1c855fda29332fc44ee3785b6bdffab96525e Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Wed, 13 Nov 2024 12:14:27 +0100 Subject: [PATCH 07/12] (+) recursive option to summary script. The summary script can not summarize a folder that contains folders of runs. It will create one hdf5 file per subfolder. --- bin/summary_script/Readme.md | 78 +-- bin/summary_script/summarize.py | 808 +++++++++++++++++--------------- 2 files changed, 467 insertions(+), 419 deletions(-) diff --git a/bin/summary_script/Readme.md b/bin/summary_script/Readme.md index 3e6c2792..969fb663 100644 --- a/bin/summary_script/Readme.md +++ b/bin/summary_script/Readme.md @@ -1,7 +1,7 @@ # Simulation Data Summary Tool This Python script summarizes the data from simulation runs that have been run with the --many option. The result is saved in an HDF5 file. It offers flexible options for customization and supports a variety of output formats. -The output of the individual runs is (linearly) interpolated to one common time grid. If the trajectory starts later or ends earlier than this time grid, the data in the summary is filled with NaNs. +The output of the individual runs is (linearly) interpolated to one common time grid. If the trajectory starts later or ends earlier than this time grid, the data in the summary is filled with NaNs. ## Usage @@ -13,78 +13,82 @@ python summarize.py -i [options] ## Options -- **`-i`, `--input`** - Specifies the simulation directory to summarize. +- **`-i`, `--input`** + Specifies the simulation directory to summarize. **Default**: `.` (current directory) -- **`-o`, `--output`** - Specifies the output path for the summary file. +- **`-o`, `--output`** + Specifies the output path for the summary file. **Default**: `./summary.hdf5` -- **`-b`, `--buf`** - Buffer size before writing data to the output file. +- **`-b`, `--buf`** + Buffer size before writing data to the output file. **Default**: `500` -- **`-f`, `--force`** - Forces overwriting the output file if it already exists. +- **`-f`, `--force`** + Forces overwriting the output file if it already exists. **Default**: `False` -- **`-v`, `--verbose`** - Enables verbose output, which is logged to `debug.log`. +- **`-v`, `--verbose`** + Enables verbose output, which is logged to `debug.log`. **Default**: `False` -- **`--time_file`** - Specifies the path to a file containing the time grid in seconds. +- **`--time_file`** + Specifies the path to a file containing the time grid in seconds. **Default**: `None` -- **`--time_final`** - Specifies the final time for the time grid. This is only used if `--time_file` is not given. +- **`--time_final`** + Specifies the final time for the time grid. This is only used if `--time_file` is not given. **Default**: Read from the template file. -- **`--time_initial`** - Specifies the initial time for the time grid. This is only used if `--time_file` is not given. +- **`--time_initial`** + Specifies the initial time for the time grid. This is only used if `--time_file` is not given. **Default**: `1e-5` -- **`--time_number`** - Specifies the number of time steps for the time grid. This is only used if `--time_file` is not given. +- **`--time_number`** + Specifies the number of time steps for the time grid. This is only used if `--time_file` is not given. **Default**: `200` -- **`--sunet_path`** - Specifies the path to the `sunet` file. +- **`-r`, `--recursive`** + Specifies if only one run folder should be summarized or all that are contained in a specific folder. + **Default**: `False` + +- **`--sunet_path`** + Specifies the path to the `sunet` file. **Default**: Read from the template. ### Output Disabling Options You can choose to disable certain parts of the summary process using the following options: -- **`--disable_mainout`** - Disables summarizing the `mainout` output. +- **`--disable_mainout`** + Disables summarizing the `mainout` output. **Default**: `False` -- **`--disable_energy`** - Disables summarizing the `energy` output. +- **`--disable_energy`** + Disables summarizing the `energy` output. **Default**: `False` -- **`--disable_timescales`** - Disables summarizing the `timescales` output. +- **`--disable_timescales`** + Disables summarizing the `timescales` output. **Default**: `False` -- **`--disable_tracked_nuclei`** - Disables summarizing the `tracked_nuclei` output. +- **`--disable_tracked_nuclei`** + Disables summarizing the `tracked_nuclei` output. **Default**: `False` -- **`--disable_snapshots`** - Disables summarizing the `snapshots` output. +- **`--disable_snapshots`** + Disables summarizing the `snapshots` output. **Default**: `False` -- **`--disable_nuloss`** - Disables summarizing the `nuloss` output. +- **`--disable_nuloss`** + Disables summarizing the `nuloss` output. **Default**: `False` - + ## Hdf5 output -The HDF5 file contains several key datasets based on the simulation results. -Note that there are tools like HDF compass to visualize the content of the Hdf5 file. +The HDF5 file contains several key datasets based on the simulation results. +Note that there are tools like HDF compass to visualize the content of the Hdf5 file. Below is a summary of the main entries in the HDF5 file: 1. **`finab/`**: @@ -121,7 +125,7 @@ Below is a summary of the main entries in the HDF5 file: 9. **`nuloss/`** (if not disabled): - **`time`**: Time grid for the neutrino loss and neutrino heating energy. - **Other Keys**: Data corresponding to the nuloss output. - + ## Examples diff --git a/bin/summary_script/summarize.py b/bin/summary_script/summarize.py index 90169e69..3d26cb95 100644 --- a/bin/summary_script/summarize.py +++ b/bin/summary_script/summarize.py @@ -52,6 +52,8 @@ help="Disable the summary of the nuloss output (default: False)") p.add_option("--disable_snapshots", action="store_true", dest="disable_snapshots", default=False, \ help="Disable the summary of the snapshots output (default: False)") +p.add_option("-r", "--recursive", action="store_true", dest="recursive", default=False, \ + help="Give a folder that contains subfolders with runs inside (default: False)") p.set_usage(""" Usage: ./summarize.py -i @@ -95,447 +97,489 @@ # Say something logger.info(f"Started summarizing run at {run_path}.") -# Get a list of all directories in the run_path. Ignore "network_data" directory -dirs = [d for d in os.listdir(run_path) if os.path.isdir(os.path.join(run_path, d)) and d != "network_data"] -# Say something -logger.info(f"Found {len(dirs)} directories in {run_path}.") - - -# Create output hdf5 file -output_file = options.outdir -# Check if the file already exists -if os.path.exists(output_file) and not options.force: - logger.error(f"Output file {output_file} already exists. Exiting.") - raise ValueError(f"Output file {output_file} already exists. Either delete or use -f option to overwrite.") -elif os.path.exists(output_file) and options.force: - logger.warning(f"Output file {output_file} already exists. Overwriting it.") -f_hdf = h5py.File(output_file, 'w') - - -# Check already one run to see what has been outputted -# Find a run that didnt crash -found = False -for d in dirs: - data = wreader(os.path.join(run_path, d)) - if not data.is_crashed: - logger.info(f"Found {d} to look up the output.") - found = True - break -if not found: - # Raise an error if all runs are crashed - logger.error("All runs are crashed!") - raise ValueError("All runs are crashed!") - -# Get the template name (ends with .par) -template_name = [f for f in os.listdir(os.path.join(run_path, d)) if f.endswith('.par')][0] -t = template(os.path.join(run_path, d, template_name)) - -# Check if the sunet is given in the template -if options.sunet_path is not None: - net_source = options.sunet_path -else: - if (not "net_source" in t.entries): - # Raise an error if the net_source is not given - raise ValueError("net_source not given in the template file.") - else: - # Get the net_source - net_source = t["net_source"] - -# Read the net_source file -logger.info(f"Using sunet file from {net_source}.") -nuclei = np.loadtxt(net_source,dtype=str) -nuclei_data = nucleus_multiple(nuclei) - - -# Create the time grid -if options.time_file is not None: - logger.info(f"Using time grid from {options.time_file}.") - mainout_time = np.loadtxt(options.time_file, dtype=float, unpack=True) -else: - if options.time_final is not None: - final_time = float(options.time_final) +if options.recursive: + # Find all folders in the run_path + folders = [f for f in os.listdir(run_path) if os.path.isdir(os.path.join(run_path, f))] + # Do it recursively + logger.info(f"Recursively going through {len(folders)} folders.") + # Create folder at output path + fold = options.outdir.replace(".hdf5","") + basepath = run_path + if not os.path.exists(fold): + os.makedirs(fold) else: - final_time = 3.15e16 - # Make some reasonable time grid - # Check if termination criterion is given - if "termination_criterion" in t.entries: - if t["termination_criterion"] == "1": - if "final_time" in t.entries: - final_time = float(t["final_time"]) - - if options.time_initial is not None: - initial_time = float(options.time_initial) + # Raise exception if the folder already exists and not force + if not options.force: + logger.error(f"Folder {fold} already exists. Exiting.") + raise ValueError(f"Folder {fold} already exists. Exiting.") + else: + logger.warning(f"Folder {fold} already exists. Overwriting it.") + os.system(f"rm -r {fold}") + os.makedirs(fold) + +# Loop over runs (if recursive) +looping = True + +while looping: + + if options.recursive: + # Get the next folder + try: + output_file = os.path.join(fold, folders[0]+".hdf5") + run_path = os.path.join(basepath, folders.pop(0)) + except IndexError: + looping = False + break else: - initial_time = 1e-5 - - if options.time_number is not None: - time_number = int(options.time_number) + # Create output hdf5 file + output_file = options.outdir + + # Get a list of all directories in the run_path. Ignore "network_data" directory + dirs = [d for d in os.listdir(run_path) if os.path.isdir(os.path.join(run_path, d)) and d != "network_data"] + + # Say something + logger.info(f"Found {len(dirs)} directories in {run_path}.") + + + # Check if the file already exists + if os.path.exists(output_file) and not options.force: + logger.error(f"Output file {output_file} already exists. Exiting.") + raise ValueError(f"Output file {output_file} already exists. Either delete or use -f option to overwrite.") + elif os.path.exists(output_file) and options.force: + logger.warning(f"Output file {output_file} already exists. Overwriting it.") + f_hdf = h5py.File(output_file, 'w') + + + # Check already one run to see what has been outputted + # Find a run that didnt crash + found = False + for d in dirs: + data = wreader(os.path.join(run_path, d)) + if not data.is_crashed: + logger.info(f"Found {d} to look up the output.") + found = True + break + if not found: + # Raise an error if all runs are crashed + if options.recursive: + logger.warning("Detected folder where all runs have crashed!") + continue + logger.error("All runs are crashed!") + raise ValueError("All runs are crashed!") + + # Get the template name (ends with .par) + template_name = [f for f in os.listdir(os.path.join(run_path, d)) if f.endswith('.par')][0] + t = template(os.path.join(run_path, d, template_name)) + + # Check if the sunet is given in the template + if options.sunet_path is not None: + net_source = options.sunet_path else: - time_number = 200 + if (not "net_source" in t.entries): + # Raise an error if the net_source is not given + raise ValueError("net_source not given in the template file.") + else: + # Get the net_source + net_source = t["net_source"] - mainout_time = np.logspace(np.log10(initial_time), np.log10(final_time), time_number) + # Read the net_source file + logger.info(f"Using sunet file from {net_source}.") + nuclei = np.loadtxt(net_source,dtype=str) + nuclei_data = nucleus_multiple(nuclei) -# Possible entries in the data -possible_entries = [] -if not options.disable_mainout: - possible_entries.append("mainout") -else: - logger.info("Ignoring mainout output.") -if not options.disable_energy: - possible_entries.append("energy") -else: - logger.info("Ignoring energy output.") -if not options.disable_timescales: - possible_entries.append("timescales") -else: - logger.info("Ignoring timescales output.") -if not options.disable_tracked_nuclei: - possible_entries.append("tracked_nuclei") -else: - logger.info("Ignoring tracked_nuclei output.") -if not options.disable_nuloss: - possible_entries.append("nuloss") -else: - logger.info("Ignoring nuloss output.") - - -entry_dict = {} -for entry in possible_entries: - # Check existence - if data.check_existence(entry) != 0: - entry_dict[entry] = {} - for key in data[entry].keys(): - # Ignore iteration and time key - if ((key == "iteration") or (key == "time") or (key == "A") or (key == "Z") or (key == "N") - or (key == "names") or (key == "latex_names")): - continue - # Ignore temperature, density, and radius for nuloss - if (entry == "nuloss") and ((key == "temp") or (key == "dens") or (key == "rad")): - continue - entry_dict[entry][key] = np.zeros((len(mainout_time),buffsize)) - - # Write the time already - f_hdf[entry+"/time"] = mainout_time - - # Say something - logger.info(f"Found {entry} in the data.") - - # Check if it is the tracked nuclei and put A, Z, and N - # if entry == "tracked_nuclei": - # f_hdf[entry+"/A"] = data[entry]["A"] - # f_hdf[entry+"/Z"] = data[entry]["Z"] - # f_hdf[entry+"/N"] = data[entry]["N"] - # f_hdf[entry+"/names"] = data[entry]["names"] - - -# Take care of snapshots, check if they are custom or not -if (data.check_existence("snapshot") != 0) and (not options.disable_snapshots): - if ("custom_snapshots" in t.entries) or ("h_custom_snapshots" in t.entries): - # Check if either ascii or hdf5 custom snapshots are given - summarize_snapshots = False - if ("custom_snapshots" in t.entries): - summarize_snapshots = (t["custom_snapshots"].strip().lower() == "yes") - # Debug statement - if (t["custom_snapshots"].strip().lower() == "yes"): - logger.info("Found custom snapshots in ascii format.") - if ("h_custom_snapshots" in t.entries): - summarize_snapshots = (summarize_snapshots or (t["h_custom_snapshots"].strip().lower() == "yes")) - # Debug statement - if (t["h_custom_snapshots"].strip().lower() == "yes"): - logger.info("Found custom snapshots in hdf5 format.") - - # Read the time for the custom snapshots - if summarize_snapshots: - if "snapshot_file" not in t.entries: - raise ValueError("Invalid template file. snapshot_file not given in the template file.") - snapshot_time = np.loadtxt(os.path.join(t["snapshot_file"]),dtype=float) - # Convert from days to seconds - snapshot_time *= 24*3600 - # Write the time already - f_hdf["snapshots/time"] = snapshot_time - # Write the A and Z data to the hdf5 file - f_hdf["snapshots/A"] = nuclei_data.A - f_hdf["snapshots/Z"] = nuclei_data.Z - f_hdf["snapshots/N"] = nuclei_data.N - # Create an array to buffer the data - snapshot_data = np.zeros((len(nuclei),len(snapshot_time),buffsize)) - logger.info(f"Summarize custom snapshots as well.") + # Create the time grid + if options.time_file is not None: + logger.info(f"Using time grid from {options.time_file}.") + mainout_time = np.loadtxt(options.time_file, dtype=float, unpack=True) else: - summarize_snapshots = False -else: - summarize_snapshots = False - if not options.disable_snapshots: - logger.info("Ignoring snapshots output.") - - -# Finab should always be in -finab_data_Y = np.zeros((len(nuclei),buffsize)) -finab_data_X = np.zeros((len(nuclei),buffsize)) -# Write already the A and Z data to the hdf5 file, this is the same for -# all runs -f_hdf["finab/A"] = nuclei_data.A -f_hdf["finab/Z"] = nuclei_data.Z -f_hdf["finab/N"] = nuclei_data.N - -# Prepare for efficient mapping -dtype_nuclei = np.dtype([('A', int), ('Z', int)]) -nuclei_struct = np.array(list(zip(nuclei_data.A.astype(int), nuclei_data.Z.astype(int))), dtype=dtype_nuclei) -# Sort the structured nuclei array and get sorting indices -nuclei_sorted_idx = np.argsort(nuclei_struct) -sorted_nuclei_struct = nuclei_struct[nuclei_sorted_idx] - -# Create array that will contain the names of the runs -# Array of strings: -run_names = np.zeros(buffsize,dtype="S100") -run_ids = np.zeros(buffsize,dtype=int) - -# Loop over all directories -ind = -1 -for counter, d in enumerate(tqdm(dirs)): - # Load data - data = wreader(os.path.join(run_path, d),silent=True) - - if data.is_crashed: - logger.warning(f"Run {d} is crashed. Skipping it.") - continue - - # Increase the index - ind += 1 - - #### Finab #### - ############### - # Put the data in the finab_data, Notice that it should be at the right A and Z position - # A is contained in data.finab["A"] and Z in data.finab["Z"]. It should fit to the nuclei_data.A and nuclei_data.Z - # All of them are 1D arrays - # Getting indices where match occurs - # indices = [(np.where((nuclei_data.A.astype(int) == A) & (nuclei_data.Z.astype(int) == Z))[0][0]) - # for A, Z in zip(data.finab["A"].astype(int), data.finab["Z"].astype(int))] - - # Check if the finab_data is full and write it to the hdf5 file - if ind % buffsize == 0 and ind != 0: - # Check if the dataset is already created and if not create it - if "finab/Y" not in f_hdf: - f_hdf.create_dataset("finab/Y", (len(nuclei),ind+1), maxshape=(len(nuclei),None)) - f_hdf.create_dataset("finab/X", (len(nuclei),ind+1), maxshape=(len(nuclei),None)) - # If necessary extend the dataset - if ind > buffsize: - f_hdf["finab/Y"].resize((len(nuclei),ind+1)) - f_hdf["finab/X"].resize((len(nuclei),ind+1)) - # Write the data to the hdf5 file - f_hdf["finab/Y"][:,ind-buffsize:ind] = finab_data_Y - f_hdf["finab/X"][:,ind-buffsize:ind] = finab_data_X - - # Convert the finab data to structured array - finab_struct = np.array(list(zip(data.finab["A"].astype(int), data.finab["Z"].astype(int))), dtype=dtype_nuclei) - # Find the matching indices - # Use np.searchsorted to find matching indices - matching_idx = np.searchsorted(sorted_nuclei_struct, finab_struct) - # Recover the original indices from the sorted index - indices = nuclei_sorted_idx[matching_idx] - - # Set first zero - finab_data_Y[:,ind % buffsize] = 0 - finab_data_X[:,ind % buffsize] = 0 - finab_data_Y[indices,ind % buffsize] = data.finab["Y"][:] - finab_data_X[indices,ind % buffsize] = data.finab["X"][:] - - #### Run name #### - ################## + if options.time_final is not None: + final_time = float(options.time_final) + else: + final_time = 3.15e16 + # Make some reasonable time grid + # Check if termination criterion is given + if "termination_criterion" in t.entries: + if t["termination_criterion"] == "1": + if "final_time" in t.entries: + final_time = float(t["final_time"]) + + if options.time_initial is not None: + initial_time = float(options.time_initial) + else: + initial_time = 1e-5 - # Check if the run_names is full and write it to the hdf5 file - if ind % buffsize == 0 and ind != 0: - # Check if the dataset is already created and if not create it - if "run_names" not in f_hdf: - f_hdf.create_dataset("run_names", (ind+1,), maxshape=(None,),dtype="S100") - # If necessary extend the dataset - if ind > buffsize: - f_hdf["run_names"].resize((ind+1,)) - # Write the data to the hdf5 file - f_hdf["run_names"][ind-buffsize:ind] = run_names - - # Save the run name - run_names[ind % buffsize] = d - - - # Get numbers out from the run name - # Check if the run_ids is full and write it to the hdf5 file - if ind % buffsize == 0 and ind != 0: - # Check if the dataset is already created and if not create it - if "run_ids" not in f_hdf: - f_hdf.create_dataset("run_ids", (ind+1,), maxshape=(None,),dtype=int) - # If necessary extend the dataset - if ind > buffsize: - f_hdf["run_ids"].resize((ind+1,)) - # Write the data to the hdf5 file - f_hdf["run_ids"][ind-buffsize:ind] = run_ids - - try: - run_ids[ind % buffsize] = int(re.findall(r'\d+', d)[-1]) - except: - run_ids[ind % buffsize] = -1 + if options.time_number is not None: + time_number = int(options.time_number) + else: + time_number = 200 - #### Custom snapshots #### - ########################## + mainout_time = np.logspace(np.log10(initial_time), np.log10(final_time), time_number) - if summarize_snapshots: - # Get the time of the snapshots - snapstime = data.snapshot_time - # Now get the indexes of the entries that agree with snapshot_time - indexes = np.searchsorted(snapstime, snapshot_time) + # Possible entries in the data + possible_entries = [] + if not options.disable_mainout: + possible_entries.append("mainout") + else: + logger.info("Ignoring mainout output.") + if not options.disable_energy: + possible_entries.append("energy") + else: + logger.info("Ignoring energy output.") + if not options.disable_timescales: + possible_entries.append("timescales") + else: + logger.info("Ignoring timescales output.") + if not options.disable_tracked_nuclei: + possible_entries.append("tracked_nuclei") + else: + logger.info("Ignoring tracked_nuclei output.") + if not options.disable_nuloss: + possible_entries.append("nuloss") + else: + logger.info("Ignoring nuloss output.") + + + entry_dict = {} + for entry in possible_entries: + # Check existence + if data.check_existence(entry) != 0: + entry_dict[entry] = {} + for key in data[entry].keys(): + # Ignore iteration and time key + if ((key == "iteration") or (key == "time") or (key == "A") or (key == "Z") or (key == "N") + or (key == "names") or (key == "latex_names")): + continue + # Ignore temperature, density, and radius for nuloss + if (entry == "nuloss") and ((key == "temp") or (key == "dens") or (key == "rad")): + continue + entry_dict[entry][key] = np.zeros((len(mainout_time),buffsize)) - # In case snapstime is shorter than snapshot_time, we need to get a mask to set it nan - if (len(snapstime) < len(snapshot_time)): - mask = np.zeros(len(snapshot_time),dtype=bool) - # Check where the time deviates more than 1e-5 - mask[np.min(np.abs(snapstime - snapshot_time[:,np.newaxis]),axis=1) > 1e-5] = True + # Write the time already + f_hdf[entry+"/time"] = mainout_time + + # Say something + logger.info(f"Found {entry} in the data.") + + # Check if it is the tracked nuclei and put A, Z, and N + # if entry == "tracked_nuclei": + # f_hdf[entry+"/A"] = data[entry]["A"] + # f_hdf[entry+"/Z"] = data[entry]["Z"] + # f_hdf[entry+"/N"] = data[entry]["N"] + # f_hdf[entry+"/names"] = data[entry]["names"] + + + # Take care of snapshots, check if they are custom or not + if (data.check_existence("snapshot") != 0) and (not options.disable_snapshots): + if ("custom_snapshots" in t.entries) or ("h_custom_snapshots" in t.entries): + # Check if either ascii or hdf5 custom snapshots are given + summarize_snapshots = False + if ("custom_snapshots" in t.entries): + summarize_snapshots = (t["custom_snapshots"].strip().lower() == "yes") + # Debug statement + if (t["custom_snapshots"].strip().lower() == "yes"): + logger.info("Found custom snapshots in ascii format.") + if ("h_custom_snapshots" in t.entries): + summarize_snapshots = (summarize_snapshots or (t["h_custom_snapshots"].strip().lower() == "yes")) + # Debug statement + if (t["h_custom_snapshots"].strip().lower() == "yes"): + logger.info("Found custom snapshots in hdf5 format.") + + # Read the time for the custom snapshots + if summarize_snapshots: + if "snapshot_file" not in t.entries: + raise ValueError("Invalid template file. snapshot_file not given in the template file.") + snapshot_time = np.loadtxt(os.path.join(t["snapshot_file"]),dtype=float) + # Convert from days to seconds + snapshot_time *= 24*3600 + # Write the time already + f_hdf["snapshots/time"] = snapshot_time + # Write the A and Z data to the hdf5 file + f_hdf["snapshots/A"] = nuclei_data.A + f_hdf["snapshots/Z"] = nuclei_data.Z + f_hdf["snapshots/N"] = nuclei_data.N + # Create an array to buffer the data + snapshot_data = np.zeros((len(nuclei),len(snapshot_time),buffsize)) + logger.info(f"Summarize custom snapshots as well.") else: - mask = None - + summarize_snapshots = False + else: + summarize_snapshots = False + if not options.disable_snapshots: + logger.info("Ignoring snapshots output.") + + + # Finab should always be in + finab_data_Y = np.zeros((len(nuclei),buffsize)) + finab_data_X = np.zeros((len(nuclei),buffsize)) + # Write already the A and Z data to the hdf5 file, this is the same for + # all runs + f_hdf["finab/A"] = nuclei_data.A + f_hdf["finab/Z"] = nuclei_data.Z + f_hdf["finab/N"] = nuclei_data.N + + # Prepare for efficient mapping + dtype_nuclei = np.dtype([('A', int), ('Z', int)]) + nuclei_struct = np.array(list(zip(nuclei_data.A.astype(int), nuclei_data.Z.astype(int))), dtype=dtype_nuclei) + # Sort the structured nuclei array and get sorting indices + nuclei_sorted_idx = np.argsort(nuclei_struct) + sorted_nuclei_struct = nuclei_struct[nuclei_sorted_idx] + + # Create array that will contain the names of the runs + # Array of strings: + run_names = np.zeros(buffsize,dtype="S100") + run_ids = np.zeros(buffsize,dtype=int) + + # Loop over all directories + ind = -1 + for counter, d in enumerate(tqdm(dirs)): + # Load data + data = wreader(os.path.join(run_path, d),silent=True) + + if data.is_crashed: + logger.warning(f"Run {d} is crashed. Skipping it.") + continue + + # Increase the index + ind += 1 + + #### Finab #### + ############### + # Put the data in the finab_data, Notice that it should be at the right A and Z position + # A is contained in data.finab["A"] and Z in data.finab["Z"]. It should fit to the nuclei_data.A and nuclei_data.Z + # All of them are 1D arrays + # Getting indices where match occurs + # indices = [(np.where((nuclei_data.A.astype(int) == A) & (nuclei_data.Z.astype(int) == Z))[0][0]) + # for A, Z in zip(data.finab["A"].astype(int), data.finab["Z"].astype(int))] + + # Check if the finab_data is full and write it to the hdf5 file + if ind % buffsize == 0 and ind != 0: + # Check if the dataset is already created and if not create it + if "finab/Y" not in f_hdf: + f_hdf.create_dataset("finab/Y", (len(nuclei),ind+1), maxshape=(len(nuclei),None)) + f_hdf.create_dataset("finab/X", (len(nuclei),ind+1), maxshape=(len(nuclei),None)) + # If necessary extend the dataset + if ind > buffsize: + f_hdf["finab/Y"].resize((len(nuclei),ind+1)) + f_hdf["finab/X"].resize((len(nuclei),ind+1)) + # Write the data to the hdf5 file + f_hdf["finab/Y"][:,ind-buffsize:ind] = finab_data_Y + f_hdf["finab/X"][:,ind-buffsize:ind] = finab_data_X - # Convert the snapshot data to structured array - finab_struct = np.array(list(zip(data.A.astype(int), data.Z.astype(int))), dtype=dtype_nuclei) + # Convert the finab data to structured array + finab_struct = np.array(list(zip(data.finab["A"].astype(int), data.finab["Z"].astype(int))), dtype=dtype_nuclei) # Find the matching indices # Use np.searchsorted to find matching indices matching_idx = np.searchsorted(sorted_nuclei_struct, finab_struct) # Recover the original indices from the sorted index - indices_nuclei = nuclei_sorted_idx[matching_idx] + indices = nuclei_sorted_idx[matching_idx] - # Check if the snapshot_data is full and write it to the hdf5 file + # Set first zero + finab_data_Y[:,ind % buffsize] = 0 + finab_data_X[:,ind % buffsize] = 0 + finab_data_Y[indices,ind % buffsize] = data.finab["Y"][:] + finab_data_X[indices,ind % buffsize] = data.finab["X"][:] + + #### Run name #### + ################## + + # Check if the run_names is full and write it to the hdf5 file if ind % buffsize == 0 and ind != 0: # Check if the dataset is already created and if not create it - if "snapshots/Y" not in f_hdf: - f_hdf.create_dataset("snapshots/Y", (len(nuclei),len(snapshot_time),ind+1), maxshape=(len(nuclei),len(snapshot_time),None)) - f_hdf.create_dataset("snapshots/X", (len(nuclei),len(snapshot_time),ind+1), maxshape=(len(nuclei),len(snapshot_time),None)) + if "run_names" not in f_hdf: + f_hdf.create_dataset("run_names", (ind+1,), maxshape=(None,),dtype="S100") # If necessary extend the dataset if ind > buffsize: - f_hdf["snapshots/Y"].resize((len(nuclei),len(snapshot_time),ind+1)) - f_hdf["snapshots/X"].resize((len(nuclei),len(snapshot_time),ind+1)) + f_hdf["run_names"].resize((ind+1,)) # Write the data to the hdf5 file - f_hdf["snapshots/Y"][:,:,ind-buffsize:ind] = snapshot_data - f_hdf["snapshots/X"][:,:,ind-buffsize:ind] = snapshot_data*nuclei_data.A[:,np.newaxis,np.newaxis] + f_hdf["run_names"][ind-buffsize:ind] = run_names - # Store it - snapshot_data[:,:,ind % buffsize] = 0 - snapshot_data[indices_nuclei,:,ind % buffsize] = data.Y[indexes][:, :].T + # Save the run name + run_names[ind % buffsize] = d - # Set entries to nan if necessary (e.g., if run does not contain a time at the beginning or end) - if mask is not None: - snapshot_data[:,mask,ind % buffsize] = np.nan + # Get numbers out from the run name + # Check if the run_ids is full and write it to the hdf5 file + if ind % buffsize == 0 and ind != 0: + # Check if the dataset is already created and if not create it + if "run_ids" not in f_hdf: + f_hdf.create_dataset("run_ids", (ind+1,), maxshape=(None,),dtype=int) + # If necessary extend the dataset + if ind > buffsize: + f_hdf["run_ids"].resize((ind+1,)) + # Write the data to the hdf5 file + f_hdf["run_ids"][ind-buffsize:ind] = run_ids - #### Other entries #### - ####################### + try: + run_ids[ind % buffsize] = int(re.findall(r'\d+', d)[-1]) + except: + run_ids[ind % buffsize] = -1 - for entry in entry_dict.keys(): - # Check if the data_dict is full and write it to the hdf5 file - if ind % buffsize == 0 and ind != 0: - for key in entry_dict[entry].keys(): + #### Custom snapshots #### + ########################## + + if summarize_snapshots: + # Get the time of the snapshots + snapstime = data.snapshot_time + + # Now get the indexes of the entries that agree with snapshot_time + indexes = np.searchsorted(snapstime, snapshot_time) + + # In case snapstime is shorter than snapshot_time, we need to get a mask to set it nan + if (len(snapstime) < len(snapshot_time)): + mask = np.zeros(len(snapshot_time),dtype=bool) + # Check where the time deviates more than 1e-5 + mask[np.min(np.abs(snapstime - snapshot_time[:,np.newaxis]),axis=1) > 1e-5] = True + else: + mask = None + + + # Convert the snapshot data to structured array + finab_struct = np.array(list(zip(data.A.astype(int), data.Z.astype(int))), dtype=dtype_nuclei) + # Find the matching indices + # Use np.searchsorted to find matching indices + matching_idx = np.searchsorted(sorted_nuclei_struct, finab_struct) + # Recover the original indices from the sorted index + indices_nuclei = nuclei_sorted_idx[matching_idx] + + # Check if the snapshot_data is full and write it to the hdf5 file + if ind % buffsize == 0 and ind != 0: # Check if the dataset is already created and if not create it - if entry+"/"+key not in f_hdf: - f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),ind+1), maxshape=(len(mainout_time),None)) + if "snapshots/Y" not in f_hdf: + f_hdf.create_dataset("snapshots/Y", (len(nuclei),len(snapshot_time),ind+1), maxshape=(len(nuclei),len(snapshot_time),None)) + f_hdf.create_dataset("snapshots/X", (len(nuclei),len(snapshot_time),ind+1), maxshape=(len(nuclei),len(snapshot_time),None)) # If necessary extend the dataset if ind > buffsize: - f_hdf[entry+"/"+key].resize((len(mainout_time),ind+1)) + f_hdf["snapshots/Y"].resize((len(nuclei),len(snapshot_time),ind+1)) + f_hdf["snapshots/X"].resize((len(nuclei),len(snapshot_time),ind+1)) # Write the data to the hdf5 file - f_hdf[entry+"/"+key][:,ind-buffsize:ind] = entry_dict[entry][key] - - # Put the data in the data_dict - for key in entry_dict[entry].keys(): - entry_dict[entry][key][:,ind % buffsize] = np.interp(mainout_time,data[entry]["time"],data[entry][key],left=np.nan,right=np.nan) - - - -# Say something -logger.info(f"Finished looping over all directories, writing the last data to the hdf5 file.") + f_hdf["snapshots/Y"][:,:,ind-buffsize:ind] = snapshot_data + f_hdf["snapshots/X"][:,:,ind-buffsize:ind] = snapshot_data*nuclei_data.A[:,np.newaxis,np.newaxis] + + # Store it + snapshot_data[:,:,ind % buffsize] = 0 + snapshot_data[indices_nuclei,:,ind % buffsize] = data.Y[indexes][:, :].T + + # Set entries to nan if necessary (e.g., if run does not contain a time at the beginning or end) + if mask is not None: + snapshot_data[:,mask,ind % buffsize] = np.nan + + + #### Other entries #### + ####################### + + for entry in entry_dict.keys(): + # Check if the data_dict is full and write it to the hdf5 file + if ind % buffsize == 0 and ind != 0: + for key in entry_dict[entry].keys(): + # Check if the dataset is already created and if not create it + if entry+"/"+key not in f_hdf: + f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),ind+1), maxshape=(len(mainout_time),None)) + # If necessary extend the dataset + if ind > buffsize: + f_hdf[entry+"/"+key].resize((len(mainout_time),ind+1)) + # Write the data to the hdf5 file + f_hdf[entry+"/"+key][:,ind-buffsize:ind] = entry_dict[entry][key] + + # Put the data in the data_dict + for key in entry_dict[entry].keys(): + entry_dict[entry][key][:,ind % buffsize] = np.interp(mainout_time,data[entry]["time"],data[entry][key],left=np.nan,right=np.nan) -# # Write the last data to the hdf5 file -#### Finab #### -############### + # Say something + logger.info(f"Finished looping over all directories, writing the last data to the hdf5 file.") -if "finab/Y" not in f_hdf: - f_hdf.create_dataset("finab/Y", (len(nuclei),ind+1), maxshape=(len(nuclei),None)) - f_hdf.create_dataset("finab/X", (len(nuclei),ind+1), maxshape=(len(nuclei),None)) -else: - f_hdf["finab/Y"].resize((len(nuclei),ind+1)) - f_hdf["finab/X"].resize((len(nuclei),ind+1)) -# Write the missing entries -if ind>buffsize: - f_hdf["finab/Y"][:,ind-buffsize+1:ind+1] = finab_data_Y[:,:buffsize] - f_hdf["finab/X"][:,ind-buffsize+1:ind+1] = finab_data_X[:,:buffsize] -else: - f_hdf["finab/Y"][:,:ind+1] = finab_data_Y[:,:ind+1] - f_hdf["finab/X"][:,:ind+1] = finab_data_X[:,:ind+1] + # # Write the last data to the hdf5 file -#### Run name #### -################## + #### Finab #### + ############### -if "run_names" not in f_hdf: - f_hdf.create_dataset("run_names", (ind+1,), maxshape=(None,),dtype="S100") -else: - f_hdf["run_names"].resize((ind+1,)) -# Write the missing entries -if ind>buffsize: - f_hdf["run_names"][ind-buffsize+1:ind+1] = run_names[:buffsize] -else: - f_hdf["run_names"][:ind+1] = run_names[:ind+1] + if "finab/Y" not in f_hdf: + f_hdf.create_dataset("finab/Y", (len(nuclei),ind+1), maxshape=(len(nuclei),None)) + f_hdf.create_dataset("finab/X", (len(nuclei),ind+1), maxshape=(len(nuclei),None)) + else: + f_hdf["finab/Y"].resize((len(nuclei),ind+1)) + f_hdf["finab/X"].resize((len(nuclei),ind+1)) + # Write the missing entries + if ind>buffsize: + f_hdf["finab/Y"][:,ind-buffsize+1:ind+1] = finab_data_Y[:,:buffsize] + f_hdf["finab/X"][:,ind-buffsize+1:ind+1] = finab_data_X[:,:buffsize] + else: + f_hdf["finab/Y"][:,:ind+1] = finab_data_Y[:,:ind+1] + f_hdf["finab/X"][:,:ind+1] = finab_data_X[:,:ind+1] -if "run_ids" not in f_hdf: - f_hdf.create_dataset("run_ids", (ind+1,), maxshape=(None,),dtype=int) -else: - f_hdf["run_ids"].resize((ind+1,)) -# Write the missing entries -if ind>buffsize: - f_hdf["run_ids"][ind-buffsize+1:ind+1] = run_ids[:buffsize] -else: - f_hdf["run_ids"][:ind+1] = run_ids[:ind+1] + #### Run name #### + ################## + if "run_names" not in f_hdf: + f_hdf.create_dataset("run_names", (ind+1,), maxshape=(None,),dtype="S100") + else: + f_hdf["run_names"].resize((ind+1,)) + # Write the missing entries + if ind>buffsize: + f_hdf["run_names"][ind-buffsize+1:ind+1] = run_names[:buffsize] + else: + f_hdf["run_names"][:ind+1] = run_names[:ind+1] -#### Custom snapshots #### -########################## -if summarize_snapshots: - if "snapshots/Y" not in f_hdf: - f_hdf.create_dataset("snapshots/Y", (len(nuclei),len(snapshot_time),ind+1), maxshape=(len(nuclei),len(snapshot_time),None)) - f_hdf.create_dataset("snapshots/X", (len(nuclei),len(snapshot_time),ind+1), maxshape=(len(nuclei),len(snapshot_time),None)) + if "run_ids" not in f_hdf: + f_hdf.create_dataset("run_ids", (ind+1,), maxshape=(None,),dtype=int) else: - f_hdf["snapshots/Y"].resize((len(nuclei),len(snapshot_time),ind+1)) - f_hdf["snapshots/X"].resize((len(nuclei),len(snapshot_time),ind+1)) + f_hdf["run_ids"].resize((ind+1,)) # Write the missing entries if ind>buffsize: - f_hdf["snapshots/Y"][:,:,ind-buffsize+1:ind+1] = snapshot_data[:,:,:buffsize] - f_hdf["snapshots/X"][:,:,ind-buffsize+1:ind+1] = snapshot_data[:,:,:buffsize]*nuclei_data.A[:,np.newaxis,np.newaxis] + f_hdf["run_ids"][ind-buffsize+1:ind+1] = run_ids[:buffsize] else: - f_hdf["snapshots/Y"][:,:,:ind+1] = snapshot_data[:,:,:ind+1] - f_hdf["snapshots/X"][:,:,:ind+1] = snapshot_data[:,:,:ind+1]*nuclei_data.A[:,np.newaxis,np.newaxis] + f_hdf["run_ids"][:ind+1] = run_ids[:ind+1] -#### Other entries #### -####################### + #### Custom snapshots #### + ########################## -for entry in entry_dict.keys(): - for key in entry_dict[entry].keys(): - if entry+"/"+key not in f_hdf: - f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),ind+1), maxshape=(len(mainout_time),None)) + if summarize_snapshots: + if "snapshots/Y" not in f_hdf: + f_hdf.create_dataset("snapshots/Y", (len(nuclei),len(snapshot_time),ind+1), maxshape=(len(nuclei),len(snapshot_time),None)) + f_hdf.create_dataset("snapshots/X", (len(nuclei),len(snapshot_time),ind+1), maxshape=(len(nuclei),len(snapshot_time),None)) else: - f_hdf[entry+"/"+key].resize((len(mainout_time),ind+1)) + f_hdf["snapshots/Y"].resize((len(nuclei),len(snapshot_time),ind+1)) + f_hdf["snapshots/X"].resize((len(nuclei),len(snapshot_time),ind+1)) # Write the missing entries if ind>buffsize: - f_hdf[entry+"/"+key][:,ind-buffsize+1:ind+1] = entry_dict[entry][key][:,:buffsize] + f_hdf["snapshots/Y"][:,:,ind-buffsize+1:ind+1] = snapshot_data[:,:,:buffsize] + f_hdf["snapshots/X"][:,:,ind-buffsize+1:ind+1] = snapshot_data[:,:,:buffsize]*nuclei_data.A[:,np.newaxis,np.newaxis] else: - f_hdf[entry+"/"+key][:,:ind+1] = entry_dict[entry][key][:,:ind+1] + f_hdf["snapshots/Y"][:,:,:ind+1] = snapshot_data[:,:,:ind+1] + f_hdf["snapshots/X"][:,:,:ind+1] = snapshot_data[:,:,:ind+1]*nuclei_data.A[:,np.newaxis,np.newaxis] + #### Other entries #### + ####################### -# Say something -logger.info(f"Finished summarizing run at {run_path}.") + for entry in entry_dict.keys(): + for key in entry_dict[entry].keys(): + if entry+"/"+key not in f_hdf: + f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),ind+1), maxshape=(len(mainout_time),None)) + else: + f_hdf[entry+"/"+key].resize((len(mainout_time),ind+1)) + # Write the missing entries + if ind>buffsize: + f_hdf[entry+"/"+key][:,ind-buffsize+1:ind+1] = entry_dict[entry][key][:,:buffsize] + else: + f_hdf[entry+"/"+key][:,:ind+1] = entry_dict[entry][key][:,:ind+1] + + + + # Say something + logger.info(f"Finished summarizing run at {run_path}.") + + # Close the hdf5 file + f_hdf.close() -# Close the hdf5 file -f_hdf.close() + if not options.recursive: + looping = False From ac4e98f9d19f984c893672d00dfee0b8112c2a0b Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Wed, 13 Nov 2024 17:11:11 +0100 Subject: [PATCH 08/12] (+) features to interactive movie Added data when clicked on the plot --- bin/movie_script/README.md | 3 + bin/movie_script/requirements.txt | 1 + bin/movie_script/src_files/FlowAnimation.py | 170 ++++++++++++++-- bin/movie_script/src_files/ngamma_eq.py | 215 ++++++++++++++++++++ bin/movie_script/src_files/winvn_class.py | 169 +++++++++++++++ bin/movie_script/winnet_movie.py | 20 +- 6 files changed, 558 insertions(+), 20 deletions(-) create mode 100644 bin/movie_script/src_files/ngamma_eq.py create mode 100644 bin/movie_script/src_files/winvn_class.py diff --git a/bin/movie_script/README.md b/bin/movie_script/README.md index 03ed5fc7..b6ad8716 100644 --- a/bin/movie_script/README.md +++ b/bin/movie_script/README.md @@ -126,6 +126,9 @@ This command will display the movie in a separate window. Note that this process - `--ye_max=YE_MAX` Upper limit of the electron fraction. +- `--indicate_r_path` + Whether or not to indicate a theoretical r-process path that has been calculated assuming (n,gamma)(gamma,n) equilibrium. + - `--frame_min=FRAME_MIN` Value of the first frame (default: 1). diff --git a/bin/movie_script/requirements.txt b/bin/movie_script/requirements.txt index c918edf5..9429d10f 100644 --- a/bin/movie_script/requirements.txt +++ b/bin/movie_script/requirements.txt @@ -1,5 +1,6 @@ h5py matplotlib>=3.8.4 +numba mpi4py numpy pandas diff --git a/bin/movie_script/src_files/FlowAnimation.py b/bin/movie_script/src_files/FlowAnimation.py index 293ca320..dab43455 100644 --- a/bin/movie_script/src_files/FlowAnimation.py +++ b/bin/movie_script/src_files/FlowAnimation.py @@ -7,16 +7,18 @@ import matplotlib.patches as patches import matplotlib.patheffects as PathEffects from matplotlib.collections import PatchCollection -from tqdm import tqdm -from matplotlib import cm -from matplotlib.patches import Arrow, FancyBboxPatch -from matplotlib.colors import LogNorm, SymLogNorm -from matplotlib.colors import ListedColormap, LinearSegmentedColormap -from matplotlib.animation import FuncAnimation -from matplotlib.widgets import Slider, Button -from wreader import wreader -from h5py import File +from tqdm import tqdm +from matplotlib import cm +from matplotlib.patches import Arrow, FancyBboxPatch +from matplotlib.colors import LogNorm, SymLogNorm +from matplotlib.colors import ListedColormap, LinearSegmentedColormap +from matplotlib.animation import FuncAnimation +from matplotlib.widgets import Slider, Button +from wreader import wreader +from h5py import File from nucleus_multiple_class import nucleus_multiple +from ngamma_eq import ngamma_eq +from winvn_class import winvn ################################################################################ @@ -77,6 +79,8 @@ def __init__( temperaturerange = (0, 10), yerange = (0.0, 0.55), plot_logo = True, + indicate_r_path = False, + winvn_path = None, interactive = False ): """ @@ -158,6 +162,10 @@ def __init__( Range of the electron fraction axis in the mainout plot. plot_logo : bool Plot the WinNet logo. + indicate_r_path : bool + Indicate the r-process path. + winvn_path : str + Path to the winvn file in case r-process path should be indicated. interactive : bool Enable interactive mode. """ @@ -182,9 +190,6 @@ def __init__( # WinNet run path self.path = path - - - # Save the parameters in class variables self.X_min = X_min # Minimum value for the mass fractions self.X_max = X_max # Maximum value for the mass fractions @@ -221,6 +226,8 @@ def __init__( self.flow_adapt_prange = flow_adapt_prange # Adapt the color range of the flow to the data self.fission_minflow = fission_minflow # Minimum value for the fission flow self.amainoutrange = amainoutrange # Range of the additional mainout plot + self.indicate_r_path = indicate_r_path # Indicate the r-process path + self.winvn_path = winvn_path # Path to the winvn file in case r-process path should be indicated if (self.flow_adapt_prange): self.flow_prange = flow_prange # Range (in log10) of the flow @@ -320,8 +327,29 @@ def __init__( self.flow_max_offset = 0.0 self.flow_min_offset = 0.0 + if self.indicate_r_path: + self.__init_ngamma_eq() + if self.interactive: self.__init_sunet_indicator() + self.__interactive_box = None + self.__interactive_textbox = None + self.winvn = winvn(self.winvn_path) + self.winvn.read_winvn() + df = self.winvn.get_dataframe() + # Set a tuple of N and Z as index + df.set_index(['N','Z'], inplace=True) + self.winvn.set_dataframe(df) + + + def __init_ngamma_eq(self): + """ + Initialize the ngamma_eq class. + """ + self.ngamma_eq = ngamma_eq(self.winvn_path) + self.ngamma_eq_plot = self.ax.plot([],[],color='purple',lw=1.5,zorder=99) + self.ngamma_eq_plot_o = self.ax.plot([],[],color='w',lw=2.5,zorder=98) + def __init_sunet_indicator(self): @@ -577,8 +605,12 @@ def __init_interactive(self): # Add a bookmark at a certain time in the slider # Calculate neutron freeze-out time - # nfreezeout = np.argmin(abs(1-self.wreader.mainout['yn']/self.wreader.mainout['yheavy'])) - # self.ax_slider.axvline(nfreezeout, color='red', linestyle='--', linewidth=1) # Bookmark indicators + min_val = 1-self.wreader.mainout['yn']/self.wreader.mainout['yheavy'] + nfreezeout = np.argmin(abs(min_val)) + # Check if its really the freeze-out time by checking if it switches from larger one to smaller 1 + if (min_val[nfreezeout-1] < 0) and (min_val[nfreezeout+1] > 0): + # self.ax_slider.plot([nfreezeout,nfreezeout],[0,0.25], color='tab:red', linestyle='-', linewidth=1) # Bookmark indicators + self.ax_slider.axvline(nfreezeout, color='tab:red', linestyle='-', linewidth=1) # Bookmark indicators # Check which data could be shown self.available_data = [] @@ -625,11 +657,23 @@ def __init_interactive(self): # Check if the sunet path exists supath = self.wreader.template['net_source'] + addbutton = 0 if os.path.exists(supath): self.sunet_button = Button(plt.axes([0.18-0.018+0.015*(len(self.toggle_buttons)+1+0.2), 0.05, 0.012, 0.022]), "S") self.sunet_button.label.set_fontsize(12) self.sunet_button.label.set_color('k') self.sunet_button.on_clicked(self.sunet_button_event) + addbutton += 1 + if self.wreader.check_existence('mainout') !=0: + if not self.indicate_r_path: + self.__init_ngamma_eq() + self.ngamma_eq_plot[0].set_visible(self.indicate_r_path) + self.ngamma_eq_plot_o[0].set_visible(self.indicate_r_path) + self.r_path_button = Button(plt.axes([0.18-0.018+0.015*(len(self.toggle_buttons)+1+addbutton+0.2), 0.05, 0.012, 0.022]), "r") + self.r_path_button.label.set_fontsize(12) + self.r_path_button.label.set_color('k') + self.r_path_button.on_clicked(self.r_path_button_event) + addbutton += 1 # Check if flow is plotted and add a button to change the flow range if self.plot_flow: @@ -681,7 +725,11 @@ def flow_button_event(self, event): self.fig.canvas.draw_idle() pass - + def r_path_button_event(self, event): + self.indicate_r_path = not self.indicate_r_path + self.ngamma_eq_plot[0].set_visible(self.indicate_r_path) + self.ngamma_eq_plot_o[0].set_visible(self.indicate_r_path) + self.fig.canvas.draw_idle() def sunet_button_event(self, event): self.sunet_indication = not self.sunet_indication @@ -778,6 +826,9 @@ def init_data(self): self.mainout_temperature = self.wreader.mainout['temp'] self.mainout_ye = self.wreader.mainout['ye'] + if self.indicate_r_path: + self.__init_ngamma_eq() + self.time = 0 # Set up the Abar @@ -1005,9 +1056,9 @@ def __init_plot_timescales(self): def __init_plot_addmainout(self): self.addmainout_plot = [self.axAddMainout.plot(self.addmainout_time,self.addmainout_data[k], - label=self.addmainout_label[i], lw=1) for i,k in enumerate(self.addmainout_data.keys())] + label=self.addmainout_label[i], lw=2) for i,k in enumerate(self.addmainout_data.keys())] # Also make the background of the box non-transparent - self.axAddMainout.legend(loc='upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) + self.axAddMainout.legend(loc='upper right', ncol=1, bbox_to_anchor=(1.15, 1.0), frameon=True, facecolor='white', edgecolor='black', framealpha=1.0, fontsize=8) def __init_plot_energy(self): @@ -1100,6 +1151,10 @@ def update_data(self, ii): self.mainout_temperature = self.wreader.mainout['temp'][:ii] self.mainout_ye = self.wreader.mainout['ye'][:ii] + if self.indicate_r_path: + self.ngamma_eq.calc_r_process_path(self.wreader.mainout['dens'][ii],self.wreader.mainout['temp'][ii],self.wreader.mainout['yn'][ii]) + + if ii == 0: return # no flows in first timestep if self.plot_flow: @@ -1259,6 +1314,13 @@ def update_flow_plot(self,): a.set_array(self.flow) self.flow_patch = self.ax.add_collection(a) + def update_ngamma_plot(self,): + if self.indicate_r_path: + self.ngamma_eq_plot[0].set_xdata(self.ngamma_eq.path_N) + self.ngamma_eq_plot[0].set_ydata(self.ngamma_eq.path_Z) + self.ngamma_eq_plot_o[0].set_xdata(self.ngamma_eq.path_N) + self.ngamma_eq_plot_o[0].set_ydata(self.ngamma_eq.path_Z) + def update_frame(self, ii): self.update_data(ii) @@ -1266,10 +1328,26 @@ def update_frame(self, ii): self.update_abun_plot() self.update_fission_plot() self.update_flow_plot() + self.update_ngamma_plot() self.update_slider(ii) + self.update_interactive_text(ii) return ii + def update_interactive_text(self, ii): + if self.interactive: + if self.__interactive_textbox is not None: + # Get the text and only change the last line + text = self.__interactive_textbox.get_text() + text = text.split("\n") + N = int(text[1].split(" = ")[1]) + Z = int(text[2].split(" = ")[1]) + X = 10**self.abun[N,Z] + if X < self.X_min: + X = 0 + text[-1] = f"X = {X:.2e}" + self.__interactive_textbox.set_text("\n".join(text)) + def save_frame(self, ii): self.update_frame(ii) # self.fig.canvas.draw() @@ -1322,7 +1400,6 @@ def pause_movie(self, click_event): self.play_button.label.set_text("❚❚") self.play_button.label.set_color("red") # Update the appearance of the button - self.fig.canvas.draw_idle() else: self.animation.pause() self.movie_paused = True @@ -1330,7 +1407,6 @@ def pause_movie(self, click_event): self.play_button.label.set_color("green") # Update the appearance of the button - self.fig.canvas.draw_idle() def on_slider_click(self, event): if event.inaxes == self.ax_slider: @@ -1338,6 +1414,62 @@ def on_slider_click(self, event): self.movie_paused = True self.play_button.label.set_text(" ▶") self.play_button.label.set_color("green") + elif event.inaxes == self.ax: + toolbar = plt.get_current_fig_manager().toolbar + active_tool = toolbar.mode # Get the current active tool + if active_tool == '': + if event.dblclick: + self.__toggle_zoom() + else: + # Get the coordinates of the click + x, y = event.xdata, event.ydata + nucl_n = int(np.round(x)) + nucl_z = int(np.round(y)) + # Check if abundances are nan there + if np.isnan(self.abun[nucl_n, nucl_z]): + if not (self.__interactive_box is None): + # Remove the rectangle if it exists + self.__interactive_box.remove() + self.__interactive_box = None + if not (self.__interactive_textbox is None): + # Remove the textbox if it exists + self.__interactive_textbox.remove() + self.__interactive_textbox = None + else: + if not (self.__interactive_box is None): + # Remove the rectangle if it exists + self.__interactive_box.remove() + self.__interactive_box = None + if not (self.__interactive_textbox is None): + # Remove the textbox if it exists + self.__interactive_textbox.remove() + self.__interactive_textbox = None + + # Create a rectangle there + self.__interactive_box = self.ax.add_patch( + patches.Rectangle((nucl_n-0.5, nucl_z-0.5), 1, 1, linewidth=1, edgecolor='r', facecolor='none')) + + # Create textbox with information + df = self.winvn.get_dataframe() + # Get the name of the nucleus + nucl_name = df.loc[(nucl_n, nucl_z), 'name'] + text = nucl_name.capitalize() + "\n" + text+= f"N = {nucl_n}\nZ = {nucl_z}\nA = {nucl_n+nucl_z}" + # Add mass fraction + X = 10**self.abun[nucl_n, nucl_z] + # Set 0 if below limit + if X < self.X_min: + X = 0 + text+= f"\nX = {X:.2e}" + + + # Also make a border around it + self.__interactive_textbox = self.ax.text(0.44, 0.23, text, transform=self.ax.transAxes, fontsize=10, + horizontalalignment='left', + verticalalignment='bottom', bbox=dict(facecolor='white', + edgecolor='black', boxstyle='round,pad=0.5')) + self.fig.canvas.draw_idle() + def on_slider_release(self, event): # Reset slider_dragging to False when the mouse is released diff --git a/bin/movie_script/src_files/ngamma_eq.py b/bin/movie_script/src_files/ngamma_eq.py new file mode 100644 index 00000000..54a3fb0b --- /dev/null +++ b/bin/movie_script/src_files/ngamma_eq.py @@ -0,0 +1,215 @@ +import numpy as np +import pandas as pd +from winvn_class import winvn +from numba import jit + + +class ngamma_eq(object): + + def __init__(self, path_winvn="winvne_v2.0.dat"): + """ + Constructor for the class + """ + # Set the paths + self.__path_winvn = path_winvn + + # Read the files + self.__read_winvn() + self.__calc_Sn_winvn() + + # Define constants + self.__kb = 8.617343e-11 # MeV/K + self.__mu = 1.660539e-24 # g + self.__hix = 1.036427e-18 + self.__NA = 6.02214076e23 # 1/mol + self.__hbar = 6.582119e-22 # MeV s + + + + def __read_winvn(self): + """ + Read the file containing the partition functions + """ + w = winvn(self.__path_winvn) + w.read_winvn() + df = w.get_dataframe() + pf = df["function"].values + Z = df["Z"].values + N = df["N"].values + spin = df["spin"].values + + # Create 2D array with Z and N, fill non existing with nans + # pf is of type function + self.__pf_Z_N = np.zeros((Z.max()+1,N.max()+1),dtype=object) + self.__spin_Z_N = np.zeros((Z.max()+1,N.max()+1)) + self.__pf_Z_N[:] = lambda x: np.nan + self.__pf_Z_N[Z,N] = pf + + self.__spin_Z_N[Z,N] = spin + + + def __calc_Sn_winvn(self): + """ + Calculate the neutron separation energy from the mass excess in the winvn + """ + w = winvn(self.__path_winvn) + w.read_winvn() + df = w.get_dataframe() + + mexc_n = df.loc["n","mass excess"] + + # Put mass excesses in 2D array + Zwinvn = df["Z"].values + Nwinvn = df["N"].values + mexc = df["mass excess"].values + + # Create 2D array with Z and N, fill non existing with nans + self.__Sn_Z_N_winvn = np.zeros((Zwinvn.max()+1,Nwinvn.max()+1)) + self.__Sn_Z_N_winvn[:] = np.nan + self.__Sn_Z_N_winvn[Zwinvn,Nwinvn] = mexc + + # Now calculate the neutron separation energy + self.__Sn_Z_N_winvn[:,1:] = (self.__Sn_Z_N_winvn[:,0:-1] + mexc_n) - self.__Sn_Z_N_winvn[:,1:] + + # Put this into the Sn array + self.__Sn_Z_N = self.__Sn_Z_N_winvn + # ALso A Z and N + self.__A = df["A"].values + self.__Z = df["Z"].values + self.__N = df["N"].values + + + + @staticmethod + @jit(nopython=True) + def helper_calc(Sn_array,density,temperature,yn,pf,spin): + """ + Helper function to calculate the path of the r-process. Necessary to use numba. + """ + + def helper_calc_ratio(Z, N, T, ndens, Sn_Z_N, pf_Z_N, spin_Z_N): + # Define constants + kb = 8.617343e-11 # MeV/K + hix = 1.036427e-18 + NA = 6.02214076e23 # 1/mol + hbar = 6.582119e-22 # MeV s + + # kbT in MeV + kbT = kb * T * 1e9 + A = Z + N + Snp1 = Sn_Z_N[Z, N + 1] + Sn = Sn_Z_N[Z, N] + pf = pf_Z_N[Z, N] + pf1 = pf_Z_N[Z, N + 1] + + sp = 2 * spin_Z_N[Z, N] + 1 + sp1 = 2 * spin_Z_N[Z, N + 1] + 1 + + a = np.log((A + 1) / A) * 3 / 2 + b = (Snp1) / (kbT) + c = np.log(pf * sp / (2 * pf1 * sp1)) + + d = np.log(2 * np.pi * hbar**2 / (hix * kbT)) * 1.5 + e = np.log(ndens * NA) + + ratio = a + b + c + d + e + return ratio + + ndens = density*yn + + # Make zerolike self.__S2n_Z_N_winvn + Zmax = Sn_array.shape[0]-1 + Nmax = Sn_array.shape[1]-1 + logratios = np.zeros((Zmax+1, Nmax+1))*np.nan + + path_Z = [] + path_N = [] + for Z in range(0,Zmax): + for N in range(0,Nmax): + if np.isnan(Sn_array[Z,N]): + continue + + logratios[Z,N] = helper_calc_ratio(Z,N,temperature,ndens,Sn_array,pf,spin) + + # Find the first occurence of ratio <= 1 + if np.all(np.isnan(logratios[Z,:])): + continue + if Z<=2: + continue + + log_ratios = logratios[Z, :] + log_cumprod = np.nancumsum(log_ratios) # Cumulative sum of logarithmic values + + maxval = np.where(~np.isnan(Sn_array[Z,:]) & (Sn_array[Z,:]>0))[0][-1] + minval = np.where(~np.isnan(Sn_array[Z,:]))[0][0] + + idx = np.minimum(np.maximum(np.argmax(log_cumprod)+1,minval),maxval) + + if len(path_Z) >= 1: + if path_Z[-1] != Z: + path_Z.append(Z) + path_N.append(path_N[-1]-1) + + path_Z.append(Z) + path_N.append(idx) + + return path_Z, path_N + + + + def calc_r_process_path(self,density,temperature,yn): + """ + Get the path of the r-process + """ + + # Precompute the shape once + rows, cols = self.__pf_Z_N.shape + + # Use np.empty to pre-allocate the pf array + pf = np.empty((rows, cols)) + def apply_callable(Z, N): + func = self.__pf_Z_N[Z, N] + if callable(func): + return func(temperature) + return np.nan # or some default value if not callable + + # Apply the function across the array using np.vectorize + vectorized_calc = np.vectorize(apply_callable) + pf = vectorized_calc(np.arange(rows)[:, None], np.arange(cols)) + + self.path_Z, self.path_N = self.helper_calc(self.__Sn_Z_N,density,temperature,yn,pf,self.__spin_Z_N) + + + # @jit(nopython=True) + def __calc_ratio(self,Z,N,T,ndens): + # kbT in MeV + kbT = self.__kb*T*1e9 + A = Z+N + Snp1 = self.__Sn_Z_N[Z,N+1] + Sn = self.__Sn_Z_N[Z,N] + pf = self.__pf_Z_N[Z,N](T) + pf1 = self.__pf_Z_N[Z,N+1](T) + + sp = 2*self.__spin_Z_N[Z,N]+1 + sp1 = 2*self.__spin_Z_N[Z,N+1]+1 + + a = np.log((A+1)/A)*3/2 + b = (Snp1)/(kbT) + # with warnings.catch_warnings(): + # warnings.simplefilter("ignore", RuntimeWarning) + c = np.log(pf*sp/(2*pf1*sp1)) + + d = np.log(2*np.pi*self.__hbar**2 / (self.__hix*kbT)) * 1.5 + e = np.log(ndens*self.__NA) + + # with warnings.catch_warnings(): + # warnings.simplefilter("ignore", RuntimeWarning) + ratio = np.exp(a+b+c+d+e,dtype=np.float128) + + return ratio + +if __name__ == "__main__": + + # Create the object + ng = ngamma_eq("/home/mreichert/data/Networks/comparison_winNet/WinNet-dev/data/winvne_v2.0.dat") + ng.calc_r_process_path(1e6,4,5e-1) diff --git a/bin/movie_script/src_files/winvn_class.py b/bin/movie_script/src_files/winvn_class.py new file mode 100644 index 00000000..50eb6b3f --- /dev/null +++ b/bin/movie_script/src_files/winvn_class.py @@ -0,0 +1,169 @@ +import pandas as pd +import numpy as np +from scipy.interpolate import interp1d + + + +class winvn(object): + + def __init__(self,path): + """ + Class to read and manage a winvn + """ + self.__path = path + + + def reset_winvn(self): + self.__df = self.__df_reset + + def read_winvn(self): + """ + Read the winvn. (stolen from Carlos) + """ + with open(self.__path,'r') as winvn: + """Open file with the context manager and extract data.""" + # read file content + winvn.readline() + + # read the ugly log temperature string + self.T_string = winvn.readline().strip() + str_size = 3 + logT = np.array([int(self.T_string[i:i+str_size]) + for i in range(0,len(self.T_string),str_size)]) + logT[-1]*=10 # I think there is a factor 10 missing!!! + logT = logT*1e-2 + + # skip the hacky name part, i.e. look for the last name, + # it appears twice + name1,name2 = ("","-") + nameList = [] + while (name1!=name2): + name2 = name1 + name1 = winvn.readline().strip() + nameList.append(name1) + + # get rid of the last name which appears twice... + #...why don't they just give the number of elements!!! + nameList.pop() + + + # initialize element data container + eleList = [] + + + # now, get the data for each of these elements + for element in nameList: + # extract element data + #..what is mass excess again??!! I hate c12 related quantities!! + wv_l = winvn.readline().split() + if len(wv_l)==7: + name, A, Z, N, sp, mass_excess, no_idea = wv_l + else: + name, A, Z, N, sp, mass_excess = wv_l + no_idea="unknown" + # I don't trust that guys! Let's check the order! + if (name != element): + sys.exit("OMG!!!") + # extract partition function + pf_list = [] + # read three lines... + for i in range(3): + pf_list.extend(winvn.readline().split()) + pf_values = np.array(pf_list,dtype=float) + + func = interp1d(logT,pf_values,bounds_error=False,kind="cubic",fill_value="extrapolate") + # save element data in a list + eleList.append([int(Z),int(N),name,float(A),float(sp), + float(mass_excess),str(no_idea), pf_values,func] ) + + # give the element data list entries names + column_names = ['Z','N','name','A','spin','mass excess','mass model', + 'partition function','function'] + # build table + self.__df = pd.DataFrame(eleList) + # set column names + self.__df.columns=column_names + self.__df = self.__df.set_index("name") + self.__df["name"] = self.__df.index + + self.__df_reset = self.__df.copy() + + # Calculate binding energies + mexc_prot = self.__df.loc["p","mass excess"] + mexc_neut = self.__df.loc["n","mass excess"] + self.__df["binding energy"] = mexc_prot*self.__df["Z"]+mexc_neut*self.__df["N"]-self.__df["mass excess"] + + def filter_with_sunet(self,nuclei): + self.__df = self.__df.loc[nuclei,:] + + def write_winvn(self,path="winvn.dat"): + """ + Save the winvn to a file. + """ + out="" + out+="\n" + out+=self.T_string+"\n" + + # Create the long list of nuclei + for ind,row in self.__df.iterrows(): + out+=row["name"].rjust(5)+"\n" + out+=row["name"].rjust(5)+"\n" + + for ind,row in self.__df.iterrows(): + out+=row["name"].rjust(5)+5*" "+"{:.3f}".format(row["A"]).rjust(7)+\ + " "+str(int(row["Z"])).rjust(3)+" "+str(int(row["N"])).rjust(3)+\ + "{:.1f}".format(row["spin"]).rjust(6)+"{:.3f}".format(row["mass excess"]).rjust(10)+\ + row["mass model"].rjust(6)+\ + "\n" + out+=" " + # Now the partition functions + for i in range(len(row['partition function'])): + out+="{:.5E}".format(row['partition function'][i]).rjust(12) + if (i+1)%8 == 0: + out+="\n " + out = out[:-1] + + with open(path,"w") as f: + f.write(out) + + + # def get_pf_Z_N(self): + + + def rate_factor(self,reactants,products,temperature): + """ + Calculate the rate factor for a given reaction + Reactants: List of nuclei of the reactants of the inverse reaction + Products: List of nuclei of the products of the inverse reaction + temperature: temperature in GK + """ + # Nominator + nom = 1e0 + for reac in reactants: + nom= nom*self.__df.loc[reac,"function"](temperature) + # Denominator + den = 1e0 + for prod in products: + den= den*self.__df.loc[prod,"function"](temperature) + factor = den/nom + return factor + + + def get_dataframe(self): + """ + Get the dataframe + """ + return self.__df + + def set_dataframe(self,value): + """ + Get the dataframe + """ + self.__df = value + + +if __name__=="__main__": + w = winvn("data/winvne_v2.0.dat") + w.read_winvn() + fac = w.rate_factor(["mg24"],["he4","ne20"],np.linspace(1,5)) + print(fac) diff --git a/bin/movie_script/winnet_movie.py b/bin/movie_script/winnet_movie.py index c85ad2bd..bd0360fb 100644 --- a/bin/movie_script/winnet_movie.py +++ b/bin/movie_script/winnet_movie.py @@ -14,7 +14,6 @@ import optparse - #--- define options ---------------------------------------------------------- p = optparse.OptionParser() p.add_option("-i","--input", action="store", dest="rundir", default='.', \ @@ -88,6 +87,9 @@ help="Lower limit of the electron fraction.") p.add_option("--ye_max", action="store", dest="ye_max", default="", \ help="Upper limit of the electron fraction.") +p.add_option("--indicate_r_path", action="store_true", dest="indicate_r_path", default=False, \ + help="Whether or not to indicate a theoretical r-process path that has been calculated assuming "+\ + "(n,gamma)(gamma,n) equilibrium.") p.add_option("--frame_min", action="store", dest="frame_min", default="", \ help="Value of the first frame (default = 1).") p.add_option("--frame_max", action="store", dest="frame_max", default="", \ @@ -167,6 +169,7 @@ if options.ye_max: kwargs['yerange'] = (kwargs['yerange'][0], float(options.ye_max)) if options.amainout_min: kwargs['amainoutrange'] = (float(options.amainout_min), kwargs['amainoutrange'][1]) if options.amainout_max: kwargs['amainoutrange'] = (kwargs['amainoutrange'][0], float(options.amainout_max)) +if options.indicate_r_path: kwargs['indicate_r_path'] = True @@ -200,6 +203,21 @@ if value == 0: print('No mainout found. Disabling mainout. Remove --additional_plot to disable this message.') kwargs['additional_plot'] = 'none' +if options.indicate_r_path: + value = w.check_existence('mainout') + if value == 0: + print('No mainout found. Disabling r-process path. Remove --indicate_r_path to disable this message.') + kwargs['indicate_r_path'] = False +if options.indicate_r_path or options.interactive: + # Check if the winvn.dat file is present + winvn_path = w.template['isotopes_file'] + if not os.path.exists(os.path.join(run_path, winvn_path)): + print('No winvn.dat found. Falling back to default winvn path.') + # relative to the file here + winvn_path = os.path.join(script_path, '../../data/winvne_v2.0.dat') + kwargs['winvn_path'] = winvn_path +if options.interactive: + kwargs['additional_plot'] = 'none' if not options.disable_mainout: value = w.check_existence('mainout') if value == 0: From 7eceff3cf0beb7c31c72a0d88179923c4493bb50 Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Wed, 13 Nov 2024 17:26:12 +0100 Subject: [PATCH 09/12] (-) bugfix in interactive movie The r-process path was sometimes not shown properly when movie was stopped. --- bin/movie_script/src_files/FlowAnimation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/movie_script/src_files/FlowAnimation.py b/bin/movie_script/src_files/FlowAnimation.py index dab43455..15d9a6c6 100644 --- a/bin/movie_script/src_files/FlowAnimation.py +++ b/bin/movie_script/src_files/FlowAnimation.py @@ -327,7 +327,7 @@ def __init__( self.flow_max_offset = 0.0 self.flow_min_offset = 0.0 - if self.indicate_r_path: + if self.indicate_r_path or self.interactive: self.__init_ngamma_eq() if self.interactive: @@ -727,6 +727,11 @@ def flow_button_event(self, event): def r_path_button_event(self, event): self.indicate_r_path = not self.indicate_r_path + if self.indicate_r_path: + # Update the data and the animation + ii = self.slider_bar.val + self.update_data(ii) + self.update_frame(ii) self.ngamma_eq_plot[0].set_visible(self.indicate_r_path) self.ngamma_eq_plot_o[0].set_visible(self.indicate_r_path) self.fig.canvas.draw_idle() From a4e5a80e78f5e520b661ef6383ec214689fc7597 Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Thu, 14 Nov 2024 21:27:32 +0100 Subject: [PATCH 10/12] (*) made summary script to also summarize detailed engen Before, if detailed energy generation was enabled, the summary script was failing. Now it is able to summarize also this. Note that this can be a huge amount of data. --- bin/summary_script/summarize.py | 73 +++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/bin/summary_script/summarize.py b/bin/summary_script/summarize.py index 3d26cb95..a32d63ed 100644 --- a/bin/summary_script/summarize.py +++ b/bin/summary_script/summarize.py @@ -11,6 +11,7 @@ from src_files.wreader import wreader from src_files.template_class import template from src_files.nucleus_multiple_class import nucleus_multiple +from scipy.interpolate import interp1d import matplotlib.pyplot as plt @@ -255,7 +256,13 @@ # Ignore temperature, density, and radius for nuloss if (entry == "nuloss") and ((key == "temp") or (key == "dens") or (key == "rad")): continue - entry_dict[entry][key] = np.zeros((len(mainout_time),buffsize)) + # Check shape of the data + if data[entry][key].ndim == 1: + entry_dict[entry][key] = np.zeros((len(mainout_time),buffsize)) + elif data[entry][key].ndim == 2: + entry_dict[entry][key] = np.zeros((len(mainout_time),data[entry][key].shape[1],buffsize)) + else: + raise ValueError(f"Invalid shape of {entry}/{key} in the data.") # Write the time already f_hdf[entry+"/time"] = mainout_time @@ -474,18 +481,34 @@ # Check if the data_dict is full and write it to the hdf5 file if ind % buffsize == 0 and ind != 0: for key in entry_dict[entry].keys(): + # Check dimensions + dim = entry_dict[entry][key].ndim # Check if the dataset is already created and if not create it - if entry+"/"+key not in f_hdf: - f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),ind+1), maxshape=(len(mainout_time),None)) - # If necessary extend the dataset - if ind > buffsize: - f_hdf[entry+"/"+key].resize((len(mainout_time),ind+1)) - # Write the data to the hdf5 file - f_hdf[entry+"/"+key][:,ind-buffsize:ind] = entry_dict[entry][key] + if dim == 2: + if entry+"/"+key not in f_hdf: + f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),ind+1), maxshape=(len(mainout_time),None)) + # If necessary extend the dataset + if ind > buffsize: + f_hdf[entry+"/"+key].resize((len(mainout_time),ind+1)) + # Write the data to the hdf5 file + f_hdf[entry+"/"+key][:,ind-buffsize:ind] = entry_dict[entry][key] + elif dim == 3: + if entry+"/"+key not in f_hdf: + f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),data[entry][key].shape[1],ind+1), maxshape=(len(mainout_time),data[entry][key].shape[1],None)) + # If necessary extend the dataset + if ind > buffsize: + f_hdf[entry+"/"+key].resize((len(mainout_time),data[entry][key].shape[1],ind+1)) + # Write the data to the hdf5 file + f_hdf[entry+"/"+key][:,:,ind-buffsize:ind] = entry_dict[entry][key] # Put the data in the data_dict for key in entry_dict[entry].keys(): - entry_dict[entry][key][:,ind % buffsize] = np.interp(mainout_time,data[entry]["time"],data[entry][key],left=np.nan,right=np.nan) + value = interp1d(data[entry]["time"],data[entry][key], + bounds_error = False, fill_value = np.nan, axis = 0)(mainout_time) + if entry_dict[entry][key].ndim == 2: + entry_dict[entry][key][:,ind % buffsize] = value + elif entry_dict[entry][key].ndim == 3: + entry_dict[entry][key][:,:,ind % buffsize] = value @@ -562,15 +585,29 @@ for entry in entry_dict.keys(): for key in entry_dict[entry].keys(): - if entry+"/"+key not in f_hdf: - f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),ind+1), maxshape=(len(mainout_time),None)) - else: - f_hdf[entry+"/"+key].resize((len(mainout_time),ind+1)) - # Write the missing entries - if ind>buffsize: - f_hdf[entry+"/"+key][:,ind-buffsize+1:ind+1] = entry_dict[entry][key][:,:buffsize] - else: - f_hdf[entry+"/"+key][:,:ind+1] = entry_dict[entry][key][:,:ind+1] + # Check dimensions + dim = entry_dict[entry][key].ndim + + if dim == 2: + if entry+"/"+key not in f_hdf: + f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),ind+1), maxshape=(len(mainout_time),None)) + else: + f_hdf[entry+"/"+key].resize((len(mainout_time),ind+1)) + # Write the missing entries + if ind>buffsize: + f_hdf[entry+"/"+key][:,ind-buffsize+1:ind+1] = entry_dict[entry][key][:,:buffsize] + else: + f_hdf[entry+"/"+key][:,:ind+1] = entry_dict[entry][key][:,:ind+1] + elif dim == 3: + if entry+"/"+key not in f_hdf: + f_hdf.create_dataset(entry+"/"+key, (len(mainout_time),data[entry][key].shape[1],ind+1), maxshape=(len(mainout_time),data[entry][key].shape[1],None)) + else: + f_hdf[entry+"/"+key].resize((len(mainout_time),data[entry][key].shape[1],ind+1)) + # Write the missing entries + if ind>buffsize: + f_hdf[entry+"/"+key][:,:,ind-buffsize+1:ind+1] = entry_dict[entry][key][:,:,:buffsize] + else: + f_hdf[entry+"/"+key][:,:,:ind+1] = entry_dict[entry][key][:,:,:ind+1] From 08b08b5d66d000ab28f3510c0850c9eecc632d84 Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Fri, 15 Nov 2024 12:31:38 +0100 Subject: [PATCH 11/12] (+) options for interactive movie Now it is possible to change the background and to toggle flows/massfractions and also to change the range of mass fractions --- bin/movie_script/src_files/FlowAnimation.py | 235 ++++++++++++++++---- bin/movie_script/src_files/winvn_class.py | 22 +- 2 files changed, 208 insertions(+), 49 deletions(-) diff --git a/bin/movie_script/src_files/FlowAnimation.py b/bin/movie_script/src_files/FlowAnimation.py index 15d9a6c6..04a135f6 100644 --- a/bin/movie_script/src_files/FlowAnimation.py +++ b/bin/movie_script/src_files/FlowAnimation.py @@ -176,6 +176,7 @@ def __init__( print('Using old version of Matplotlib ('+str(mpl.__version__)+'), some features may not work.') print('Need 3.8 or higher.') + # Set the paths # Data directory: # Remember where this file is located @@ -326,6 +327,9 @@ def __init__( # For interactive flow range self.flow_max_offset = 0.0 self.flow_min_offset = 0.0 + # For interactive mafra range + self.mafra_max_offset = 0.0 + self.mafra_min_offset = 0.0 if self.indicate_r_path or self.interactive: self.__init_ngamma_eq() @@ -336,10 +340,18 @@ def __init__( self.__interactive_textbox = None self.winvn = winvn(self.winvn_path) self.winvn.read_winvn() + self.winvn.calculate_Sn() df = self.winvn.get_dataframe() + Z = df['Z'].values + N = df['N'].values # Set a tuple of N and Z as index df.set_index(['N','Z'], inplace=True) self.winvn.set_dataframe(df) + # Prepare the other backgrounds + self.__background_Y_2 = np.zeros_like(self.__background_Y_1)*np.nan + self.__background_Y_2[N,Z] = df['binding energy'].values/(Z+N) + self.__background_Y_3 = np.zeros_like(self.__background_Y_1)*np.nan + self.__background_Y_3[N,Z] = df['Sn'].values def __init_ngamma_eq(self): @@ -589,7 +601,7 @@ def __init_logo(self): """ self.axLogo = plt.axes([0.75,0.45,0.15,0.15]) self.axLogo.axis('off') - self.axLogo.imshow(plt.imread(os.path.join(self.__data_path,'WinNet_logo.png'))) + self.logo = self.axLogo.imshow(plt.imread(os.path.join(self.__data_path,'WinNet_logo.png'))) def __init_interactive(self): @@ -603,6 +615,7 @@ def __init_interactive(self): self.fig.canvas.mpl_connect('key_press_event', self.arrow_update) self.__interactive_ax = None + # Add a bookmark at a certain time in the slider # Calculate neutron freeze-out time min_val = 1-self.wreader.mainout['yn']/self.wreader.mainout['yheavy'] @@ -675,35 +688,109 @@ def __init_interactive(self): self.r_path_button.on_clicked(self.r_path_button_event) addbutton += 1 + # Create button for background color + self.ax_button_bg = plt.axes([0.18-0.018+0.015*(len(self.toggle_buttons)+1+addbutton+0.4), 0.05, 0.012, 0.022]) + self.bg_button = Button(self.ax_button_bg, "B") + self.bg_button.label.set_fontsize(12) + self.bg_button.label.set_color('k') + self.bg_button.on_clicked(self.change_bg) + self.background = 1 + addbutton += 1 + + # Check if flow is plotted and add a button to change the flow range if self.plot_flow: self.flow_buttons = [Button(plt.axes([0.88, 0.905, 0.01, 0.02]), "+")] - self.flow_buttons[-1].label.set_fontsize(12) - self.flow_buttons[-1].label.set_color('k') - self.flow_buttons[-1].on_clicked(self.flow_button_event) - - self.flow_buttons.append(Button(plt.axes([0.868, 0.905, 0.01, 0.02]), "-")) - self.flow_buttons[-1].label.set_fontsize(12) - self.flow_buttons[-1].label.set_color('k') - self.flow_buttons[-1].on_clicked(self.flow_button_event) - - self.flow_buttons.append(Button(plt.axes([0.762, 0.905, 0.01, 0.02]),"+")) - self.flow_buttons[-1].label.set_fontsize(12) - self.flow_buttons[-1].label.set_color('k') - self.flow_buttons[-1].on_clicked(self.flow_button_event) - + self.flow_buttons.append(Button(plt.axes([0.869, 0.905, 0.01, 0.02]), "-")) + self.flow_buttons.append(Button(plt.axes([0.7605, 0.905, 0.01, 0.02]),"+")) self.flow_buttons.append(Button(plt.axes([0.75, 0.905, 0.01, 0.02]), "-")) - self.flow_buttons[-1].label.set_fontsize(12) - self.flow_buttons[-1].label.set_color('k') - self.flow_buttons[-1].on_clicked(self.flow_button_event) + # Add a button to change reset + self.flow_buttons.append(Button(plt.axes([0.771, 0.905, 0.01, 0.02]), "⟲")) + # Add button next to - to switch off + self.flow_buttons.append(Button(plt.axes([0.858, 0.905, 0.01, 0.02]),"○")) + + for f in self.flow_buttons: + f.label.set_fontsize(12) + f.label.set_color('k') + f.on_clicked(self.flow_button_event) + + # Mass fraction buttons + self.mafra_buttons = [Button(plt.axes([0.71, 0.905, 0.01, 0.02]), "+")] + self.mafra_buttons.append(Button(plt.axes([0.699, 0.905, 0.01, 0.02]), "-")) + self.mafra_buttons.append(Button(plt.axes([0.5905, 0.905, 0.01, 0.02]),"+")) + self.mafra_buttons.append(Button(plt.axes([0.58, 0.905, 0.01, 0.02]), "-")) + # Add a button to change reset + self.mafra_buttons.append(Button(plt.axes([0.601, 0.905, 0.01, 0.02]), "⟲")) + # Add button next to - to switch off + self.mafra_buttons.append(Button(plt.axes([0.688, 0.905, 0.01, 0.02]),"○")) + else: + self.mafra_buttons = [Button(plt.axes([0.88, 0.905, 0.01, 0.02]), "+")] + self.mafra_buttons.append(Button(plt.axes([0.869, 0.905, 0.01, 0.02]), "-")) + self.mafra_buttons.append(Button(plt.axes([0.7605, 0.905, 0.01, 0.02]),"+")) + self.mafra_buttons.append(Button(plt.axes([0.75, 0.905, 0.01, 0.02]), "-")) # Add a button to change reset - self.flow_buttons.append(Button(plt.axes([0.775, 0.905, 0.01, 0.02]), "⟲")) - self.flow_buttons[-1].label.set_fontsize(12) - self.flow_buttons[-1].label.set_color('k') - self.flow_buttons[-1].on_clicked(self.flow_button_event) + self.mafra_buttons.append(Button(plt.axes([0.771, 0.905, 0.01, 0.02]), "⟲")) + # Add button next to - to switch off + self.mafra_buttons.append(Button(plt.axes([0.858, 0.905, 0.01, 0.02]),"○")) + + for f in self.mafra_buttons: + f.label.set_fontsize(12) + f.label.set_color('k') + f.on_clicked(self.mafra_button_event) + + + def change_bg(self, event): + self.background = self.background + 1 + if self.background >3: + self.background = 1 + + if self.background == 1: + self.background_Y = self.__background_Y_1 + self.background_im.set_cmap(self.__background_cmap) + self.background_im.set_array(self.background_Y.T) + # also set the limits again + self.background_im.set_clim(vmin=(min(self.values)),vmax=(max(self.values))) + self.cb_bg.ax.set_visible(False) + self.logo.set_visible(True) + elif self.background == 2: + self.background_Y = self.__background_Y_2 + self.background_im.set_cmap('nipy_spectral') + self.cb_bg.set_label('BE/A [MeV]') + # Set the norm (linear) + self.background_im.set_norm( plt.Normalize(vmin=5,vmax=np.nanmax(self.background_Y))) + self.background_im.set_array((self.background_Y.T)) + self.cb_bg.ax.set_visible(True) + # Hide the WinNet logo + self.logo.set_visible(False) + elif self.background == 3: + self.background_Y = self.__background_Y_3 + + # Create a colormap with alpha + original_cmap = cm.nipy_spectral # Choose your colormap + alpha = 0.5 # Set alpha value (0.0 to 1.0) + + # Create a colormap with modified alpha + colors = original_cmap(np.linspace(0, 1, original_cmap.N)) + colors[:, -1] = alpha # Set alpha channel + transparent_cmap = ListedColormap(colors) + + self.background_im.set_cmap(transparent_cmap) + self.cb_bg.set_label('Sn [MeV]') + # Set the norm (linear) + self.background_im.set_norm( plt.Normalize(vmin=0,vmax=10)) + self.background_im.set_array((self.background_Y.T)) + self.cb_bg.ax.set_visible(True) + # Hide the WinNet logo + self.logo.set_visible(False) + + + # also set the limits again and make it log scale + # self.background_im.set_clim(vmin=np.nanmin(self.background_Y),vmax=np.nanmax(self.background_Y)) + + + self.fig.canvas.draw_idle() - # self.flow_button.on_clicked(self.flow_button_event) def flow_button_event(self, event): if event.inaxes == self.flow_buttons[0].ax: @@ -717,13 +804,46 @@ def flow_button_event(self, event): elif event.inaxes == self.flow_buttons[4].ax: self.flow_max_offset = 0.0 self.flow_min_offset = 0.0 + elif event.inaxes == self.flow_buttons[5].ax: + if self.flow_adapt_width: + self.flow_patch.set_visible(not self.flow_patch.get_visible()) + else: + self.quiver.set_visible(not self.quiver.get_visible()) + self.flow_buttons[5].label.set_text("○" if self.quiver.get_visible() else "●") + # Refresh the animation at current position if self.movie_paused: self.update_frame(self.slider_bar.val) self.fig.canvas.draw_idle() - pass + + def mafra_button_event(self, event): + if event.inaxes == self.mafra_buttons[0].ax: + self.mafra_max_offset += 0.5 + elif event.inaxes == self.mafra_buttons[1].ax: + self.mafra_max_offset -= 0.5 + elif event.inaxes == self.mafra_buttons[2].ax: + self.mafra_min_offset += 0.5 + elif event.inaxes == self.mafra_buttons[3].ax: + self.mafra_min_offset -= 0.5 + elif event.inaxes == self.mafra_buttons[4].ax: + self.mafra_max_offset = 0.0 + self.mafra_min_offset = 0.0 + elif event.inaxes == self.mafra_buttons[5].ax: + self.abun_im.set_visible(not self.abun_im.get_visible()) + self.mafra_buttons[5].label.set_text("○" if self.abun_im.get_visible() else "●") + + + norm = plt.Normalize(vmin=np.log10(self.X_min*10**self.mafra_min_offset), vmax=np.log10(self.X_max*10**self.mafra_max_offset)) + # self.abun_cbar.set_norm(norm) + self.abun_im.set_norm(norm) + # Also change the colorbar + norm = LogNorm(vmin=self.X_min*10**self.mafra_min_offset, vmax=self.X_max*10**self.mafra_max_offset, clip=True) + self.mafra_sm.set_norm(norm) + + self.fig.canvas.draw_idle() + def r_path_button_event(self, event): self.indicate_r_path = not self.indicate_r_path @@ -844,18 +964,19 @@ def init_data(self): self.Xbins = np.zeros(len(self.massBins),dtype=float) # Create custom colormap, with colormap for abundances and also the background colors - abucmap = mpl.colormaps[self.cmapNameX] - abundance_colors = abucmap(np.linspace(0, 1, 256)) + abucmap = mpl.colormaps[self.cmapNameX] + self.__abundance_colors = abucmap + + # Colors for background massbin_colormap = mpl.colormaps[self.cmapNameMassBins] amount_mass_bins = len(self.massBins) massbin_colors = massbin_colormap(np.linspace(0, 1, amount_mass_bins)) massbin_colors[:,3] = self.alphaMassBins self.massbin_colors = massbin_colors - newcolors = np.vstack((massbin_colors, abundance_colors)) - self.__abundance_colors = ListedColormap(newcolors) - # Create the values for the colors - dist = (np.log10(self.X_max)-np.log10(self.X_min))/256.0 - self.values = np.linspace(np.log10(self.X_min)-amount_mass_bins*dist, np.log10(self.X_max),num=256+amount_mass_bins+1,endpoint=True) + self.__background_cmap = ListedColormap(self.massbin_colors) + + # self.values = np.linspace(np.log10(self.X_min)-amount_mass_bins*dist, np.log10(self.X_max),num=256+amount_mass_bins+1,endpoint=True) + self.values = np.linspace(0, 1,num=amount_mass_bins,endpoint=True) # Create the background array that will contain numbers according to the background colors background_Y = np.empty((self.__max_N+1, self.__max_Z+1)) @@ -864,13 +985,14 @@ def init_data(self): mask = (self.__A_plot >= self.massBins[index][0]) & (self.__A_plot <= self.massBins[index][1]) background_Y[self.__N_plot[mask],self.__Z_plot[mask]] = self.values[index] self.__background_Y = background_Y + self.__background_Y_1 = np.copy(background_Y) # Set up the axes for the nuclear chart self.n = np.arange(0, self.__max_N+1) self.z = np.arange(0, self.__max_Z+1) # Set up the abundance array - self.abun = self.__background_Y + self.abun = np.zeros_like(self.__background_Y) * np.nan # Set up the array for the fission region self.fis_region = np.zeros_like(self.abun) @@ -886,10 +1008,14 @@ def init_plot(self): """ Initialize the plots. """ + self.background_im = self.ax.pcolormesh(self.n,self.z,self.__background_Y.T, + cmap = self.__background_cmap,vmin=(min(self.values)), + vmax=(max(self.values)),linewidth=0.0,edgecolor="face") + # Plot the nuclear chart self.abun_im = self.ax.pcolormesh(self.n,self.z,self.abun.T, - cmap = self.__abundance_colors,vmin=(min(self.values)), - vmax=(max(self.values)),linewidth=0.0,edgecolor="face") + cmap = self.__abundance_colors,vmin=(np.log10(self.X_min)), + vmax=(np.log10(self.X_max)),linewidth=0.0,edgecolor="face") if (self.plot_flow): @@ -1100,7 +1226,8 @@ def init_cbars(self, abun_cbar, flow_cbar): # Plot the abundance colorbar if abun_cbar: # Make a custom colormap since the abundance one has the background colors in as well - self.abun_cbar = self.fig.colorbar(mpl.cm.ScalarMappable(norm=LogNorm(vmin=self.X_min,vmax=self.X_max), cmap="inferno"), + self.mafra_sm = mpl.cm.ScalarMappable(norm=LogNorm(vmin=self.X_min,vmax=self.X_max), cmap="inferno") + self.abun_cbar = self.fig.colorbar(self.mafra_sm, cax=self.cax[ii], orientation='horizontal', label='') self.abun_cbar.ax.set_title('Mass fraction') ii += 1 @@ -1116,6 +1243,15 @@ def init_cbars(self, abun_cbar, flow_cbar): self.flow_cbar.ax.set_title('Flow') ii += 1 + if self.interactive: + self.ax_bg_cb = plt.axes([0.75,0.54,0.15,0.02]) + # Horizontal colorbar + self.cb_bg = plt.colorbar(self.background_im, cax=self.ax_bg_cb, orientation='horizontal') + self.cb_bg.set_label('BE/A [MeV]') + # Hide the colorbar + self.cb_bg.ax.set_visible(False) + + def update_data(self, ii): """ @@ -1125,8 +1261,9 @@ def update_data(self, ii): self.time = self.wreader.snapshot_time[ii] yy = self.wreader.Y[ii] xx = yy * self.A - self.abun = self.__background_Y.copy() - mask = xx > self.X_min + # self.abun = self.__background_Y.copy() + self.abun[:,:] = np.nan + mask = xx > self.X_min*10**self.mafra_min_offset self.abun[self.N[mask], self.Z[mask]] = np.log10(xx[mask]) self.Asum, self.Xsum = self.sum_over_A(self.A, xx) @@ -1310,14 +1447,16 @@ def update_flow_plot(self,): else: # Quiver does not allow for changing the width of the arrows # Therefore, we have to us a patchcollection and draw it everytime new. - self.flow_patch.remove() - width = (self.flow_maxArrowWidth-self.flow_minArrowWidth)/self.flow_prange - with np.errstate(divide='ignore'): - arrowwidth = (np.log10(self.flow)-np.log10(self.flow_min))*width + self.flow_minArrowWidth - flow_arrows = [Arrow(self.flow_N[i],self.flow_Z[i],self.flow_dn[i],self.flow_dz[i],width=arrowwidth[i],color='k') for i in range(len(self.flow))] - a = PatchCollection(flow_arrows, cmap=self.cmapNameFlow, norm=self.flow_norm) - a.set_array(self.flow) - self.flow_patch = self.ax.add_collection(a) + if self.flow_patch.get_visible(): + self.flow_patch.remove() + width = (self.flow_maxArrowWidth-self.flow_minArrowWidth)/self.flow_prange + with np.errstate(divide='ignore'): + arrowwidth = (np.log10(self.flow)-np.log10(self.flow_min))*width + self.flow_minArrowWidth + flow_arrows = [Arrow(self.flow_N[i],self.flow_Z[i],self.flow_dn[i],self.flow_dz[i],width=arrowwidth[i],color='k') for i in range(len(self.flow))] + a = PatchCollection(flow_arrows, cmap=self.cmapNameFlow, norm=self.flow_norm) + a.set_array(self.flow) + self.flow_patch = self.ax.add_collection(a) + def update_ngamma_plot(self,): if self.indicate_r_path: @@ -1348,7 +1487,7 @@ def update_interactive_text(self, ii): N = int(text[1].split(" = ")[1]) Z = int(text[2].split(" = ")[1]) X = 10**self.abun[N,Z] - if X < self.X_min: + if (X < self.X_min*10**self.mafra_min_offset) or np.isnan(X): X = 0 text[-1] = f"X = {X:.2e}" self.__interactive_textbox.set_text("\n".join(text)) @@ -1431,7 +1570,7 @@ def on_slider_click(self, event): nucl_n = int(np.round(x)) nucl_z = int(np.round(y)) # Check if abundances are nan there - if np.isnan(self.abun[nucl_n, nucl_z]): + if np.isnan(self.__background_Y[nucl_n, nucl_z]): if not (self.__interactive_box is None): # Remove the rectangle if it exists self.__interactive_box.remove() @@ -1463,7 +1602,7 @@ def on_slider_click(self, event): # Add mass fraction X = 10**self.abun[nucl_n, nucl_z] # Set 0 if below limit - if X < self.X_min: + if (X < (self.X_min*10**self.mafra_min_offset)) or np.isnan(X): X = 0 text+= f"\nX = {X:.2e}" diff --git a/bin/movie_script/src_files/winvn_class.py b/bin/movie_script/src_files/winvn_class.py index 50eb6b3f..4f223bac 100644 --- a/bin/movie_script/src_files/winvn_class.py +++ b/bin/movie_script/src_files/winvn_class.py @@ -127,7 +127,27 @@ def write_winvn(self,path="winvn.dat"): f.write(out) - # def get_pf_Z_N(self): + def calculate_Sn(self): + """ + Calculate the neutron separation energies + """ + mexc_n = self.__df.loc["n","mass excess"] + + # Put mass excesses in 2D array + Zwinvn = self.__df["Z"].values + Nwinvn = self.__df["N"].values + mexc = self.__df["mass excess"].values + + # Create 2D array with Z and N, fill non existing with nans + Sn_Z_N_winvn = np.zeros((Zwinvn.max()+1,Nwinvn.max()+1)) + Sn_Z_N_winvn[:] = np.nan + Sn_Z_N_winvn[Zwinvn,Nwinvn] = mexc + + # Now calculate the neutron separation energy + Sn_Z_N_winvn[:,1:] = (Sn_Z_N_winvn[:,0:-1] + mexc_n) - Sn_Z_N_winvn[:,1:] + + # Put it into df + self.__df["Sn"] = Sn_Z_N_winvn[Zwinvn,Nwinvn] def rate_factor(self,reactants,products,temperature): From c341d93fddd75340ed21bff306ee2b5df302ec5b Mon Sep 17 00:00:00 2001 From: MReichert91 Date: Fri, 15 Nov 2024 14:41:56 +0100 Subject: [PATCH 12/12] (+) description of interactive mode, poss. to turn off flows Added describtion to the readme of the flow plot. Also added a possibility to turn on/off flow arrows and mass fractions --- bin/movie_script/README.md | 33 ++++++++++++++++++++ bin/movie_script/src_files/FlowAnimation.py | 7 +++-- doc/doxygen/figures/interactive.png | Bin 0 -> 224330 bytes 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 doc/doxygen/figures/interactive.png diff --git a/bin/movie_script/README.md b/bin/movie_script/README.md index b6ad8716..9bc79f17 100644 --- a/bin/movie_script/README.md +++ b/bin/movie_script/README.md @@ -165,3 +165,36 @@ This command will display the movie in a separate window. Note that this process An example output could look like the following: ![Simulation visualization](../../doc/doxygen/figures/winteler_mhd.gif) + + +#### Interactive Mode + +The **Interactive Mode** enhances the functionality of the nuclear reaction flow visualization by enabling real-time interaction with the plots. Below are the features and instructions for using the interactive mode effectively: + + +1. **Dynamic Controls:** + - **Slider**: Allows the user to navigate through the simulation timeline seamlessly. + - **Play/Pause Button**: Toggles the animation playback. + - **Zoom Button**: Enables zooming in and out on the low mass region of the chart. + +2. **Toggle Data Layers:** + - Activate or deactivate specific plots (e.g., `tracked nuclei`, `timescales`, `energy`) with dedicated buttons. + +3. **R-process path** + - Highlight the r-process path that is calculated assuming (n,gamma) equilibrium. + +4. **Highlight sunet** + - Highlight the nuclei that have been used in the calculation with a red outline. + +5. **Neutron freezeout** + - Neutron freezeout is indicated as red vertical line on the slider. + +6. **Flow and Abundance Adjustments:** + - Fine-tune flow and abundance ranges dynamically using buttons to increment, decrement, or reset the values. + - Toggle visualization elements such as arrows for flow or background mass fractions. + +7. **Custom Backgrounds:** + - Switch between different background visualizations (e.g., Binding energy, Neutron separation energy) for a tailored view of nuclear properties. + + +![Interactive mode](../../doc/doxygen/figures/interactive.png) \ No newline at end of file diff --git a/bin/movie_script/src_files/FlowAnimation.py b/bin/movie_script/src_files/FlowAnimation.py index 04a135f6..178942c0 100644 --- a/bin/movie_script/src_files/FlowAnimation.py +++ b/bin/movie_script/src_files/FlowAnimation.py @@ -352,6 +352,7 @@ def __init__( self.__background_Y_2[N,Z] = df['binding energy'].values/(Z+N) self.__background_Y_3 = np.zeros_like(self.__background_Y_1)*np.nan self.__background_Y_3[N,Z] = df['Sn'].values + self.forward_mode = 0 def __init_ngamma_eq(self): @@ -615,7 +616,6 @@ def __init_interactive(self): self.fig.canvas.mpl_connect('key_press_event', self.arrow_update) self.__interactive_ax = None - # Add a bookmark at a certain time in the slider # Calculate neutron freeze-out time min_val = 1-self.wreader.mainout['yn']/self.wreader.mainout['yheavy'] @@ -807,9 +807,10 @@ def flow_button_event(self, event): elif event.inaxes == self.flow_buttons[5].ax: if self.flow_adapt_width: self.flow_patch.set_visible(not self.flow_patch.get_visible()) + self.flow_buttons[5].label.set_text("○" if self.flow_patch.get_visible() else "●") else: self.quiver.set_visible(not self.quiver.get_visible()) - self.flow_buttons[5].label.set_text("○" if self.quiver.get_visible() else "●") + self.flow_buttons[5].label.set_text("○" if self.quiver.get_visible() else "●") # Refresh the animation at current position @@ -1503,6 +1504,8 @@ def save_frame(self, ii): def get_funcanimation(self, frames=None, **kwargs): if frames is None: frames = range(self.n_timesteps) + if self.interactive and self.forward_mode == 1: + frames = frames[::100] self.frames=frames self.animation = FuncAnimation(self.fig, self.update_frame, frames=frames, **kwargs) diff --git a/doc/doxygen/figures/interactive.png b/doc/doxygen/figures/interactive.png new file mode 100644 index 0000000000000000000000000000000000000000..78e44ee9472cd833bb9690b1aa8ac187de234a10 GIT binary patch literal 224330 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 z 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

sB1o;Axa&~w8qw*BKk{7t~5O)dW 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

02b?Sc1nfazk1GWL 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ⅈ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 zℑ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