diff --git a/examples/HydroTests/Diffusion_1D/.gitignore b/examples/HydroTests/Diffusion_1D/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..dadfbc6e8ce185fc82169300e6f289ed8abc9bf2 --- /dev/null +++ b/examples/HydroTests/Diffusion_1D/.gitignore @@ -0,0 +1 @@ +beta_* diff --git a/examples/HydroTests/Diffusion_1D/README b/examples/HydroTests/Diffusion_1D/README new file mode 100644 index 0000000000000000000000000000000000000000..6254e0ce9a7aa1af5f54ba388b2b08af6bb78739 --- /dev/null +++ b/examples/HydroTests/Diffusion_1D/README @@ -0,0 +1,19 @@ +Diffusion 1D +============ + +This is a very simple, 1D test. It sets up a uniform density particle +distribution, with energy discontinuities generated at every particle. +Particles have their internal energy set in a binary manner, with them +alternating between "low" and "high" states. Under a standard SPH +scheme, we expect to see no evolution whatsoever in the box. However, +under a scheme with thermal diffusion, we expect that there will be +some diffusion between the particles and that the distribution of +internal energy will equalise. + +Included are some scripts to create initial conditions (`makeIC.py`), +plot a solution (`plotSolution.py`), and to run the code (`run.sh`). +Also included is a script to run a convergence test by changing the +`SPH:beta_diffusion` parameter (`run_set.sh`). + +To make the initial conditions and produce the plots, the `swiftsimio` +library (http://gitlab.cosma.dur.ac.uk/jborrow/swiftsimio) is required. \ No newline at end of file diff --git a/examples/HydroTests/Diffusion_1D/diffusion.yml b/examples/HydroTests/Diffusion_1D/diffusion.yml new file mode 100644 index 0000000000000000000000000000000000000000..099cad637bf947926baa3571a2b1361d16293547 --- /dev/null +++ b/examples/HydroTests/Diffusion_1D/diffusion.yml @@ -0,0 +1,38 @@ +# Define the system of units to use internally. +InternalUnitSystem: + UnitMass_in_cgs: 1 # Grams + UnitLength_in_cgs: 1 # Centimeters + UnitVelocity_in_cgs: 1 # Centimeters per second + UnitCurrent_in_cgs: 1 # Amperes + UnitTemp_in_cgs: 1 # Kelvin + +# Parameters governing the time integration +TimeIntegration: + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 1e-0 # The end time of the simulation (in internal units). + dt_min: 1e-7 # The minimal time-step size of the simulation (in internal units). + dt_max: 1e-2 # The maximal time-step size of the simulation (in internal units). + +# Parameters governing the snapshots +Snapshots: + basename: diffusion # Common part of the name of output files + time_first: 0. # Time of the first output (in internal units) + delta_time: 2e-2 # Time difference between consecutive outputs (in internal units) + +# Parameters governing the conserved quantities statistics +Statistics: + delta_time: 1e-1 # Time between statistics output + +# Parameters for the hydrodynamics scheme +SPH: + resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel). + CFL_condition: 0.1 # Courant-Friedrich-Levy condition for time integration. + diffusion_alpha: 0.0 # (Optional) Override the initial value for the thermal diffusion coefficient in schemes with thermal diffusion. + diffusion_beta: 0.01 # (Optional) Override the decay/rise rate tuning parameter for the thermal diffusion. + diffusion_alpha_max: 1.0 # (Optional) Override the maximal thermal diffusion coefficient that is allowed for a given particle. + diffusion_alpha_min: 0.0 # (Optional) Override the minimal thermal diffusion coefficient that is allowed for a given particle. + +# Parameters related to the initial conditions +InitialConditions: + file_name: ./diffusion.hdf5 # The file to read + periodic: 1 diff --git a/examples/HydroTests/Diffusion_1D/diffusion_fixed_alpha.yml b/examples/HydroTests/Diffusion_1D/diffusion_fixed_alpha.yml new file mode 100644 index 0000000000000000000000000000000000000000..36f70e2492a05e90ab29496d4fc064cc2c35f62f --- /dev/null +++ b/examples/HydroTests/Diffusion_1D/diffusion_fixed_alpha.yml @@ -0,0 +1,38 @@ +# Define the system of units to use internally. +InternalUnitSystem: + UnitMass_in_cgs: 1 # Grams + UnitLength_in_cgs: 1 # Centimeters + UnitVelocity_in_cgs: 1 # Centimeters per second + UnitCurrent_in_cgs: 1 # Amperes + UnitTemp_in_cgs: 1 # Kelvin + +# Parameters governing the time integration +TimeIntegration: + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 1e-0 # The end time of the simulation (in internal units). + dt_min: 1e-7 # The minimal time-step size of the simulation (in internal units). + dt_max: 1e-2 # The maximal time-step size of the simulation (in internal units). + +# Parameters governing the snapshots +Snapshots: + basename: diffusion_fixed_alpha # Common part of the name of output files + time_first: 0. # Time of the first output (in internal units) + delta_time: 2e-2 # Time difference between consecutive outputs (in internal units) + +# Parameters governing the conserved quantities statistics +Statistics: + delta_time: 1e-1 # Time between statistics output + +# Parameters for the hydrodynamics scheme +SPH: + resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel). + CFL_condition: 0.1 # Courant-Friedrich-Levy condition for time integration. + diffusion_alpha: 0.1 # (Optional) Override the initial value for the thermal diffusion coefficient in schemes with thermal diffusion. + diffusion_beta: 0.01 # (Optional) Override the decay/rise rate tuning parameter for the thermal diffusion. + diffusion_alpha_max: 0.1 # (Optional) Override the maximal thermal diffusion coefficient that is allowed for a given particle. + diffusion_alpha_min: 0.1 # (Optional) Override the minimal thermal diffusion coefficient that is allowed for a given particle. + +# Parameters related to the initial conditions +InitialConditions: + file_name: ./diffusion.hdf5 # The file to read + periodic: 1 diff --git a/examples/HydroTests/Diffusion_1D/makeIC.py b/examples/HydroTests/Diffusion_1D/makeIC.py new file mode 100644 index 0000000000000000000000000000000000000000..09a4b5c698c7351a8a08f53a268c2b333bfef991 --- /dev/null +++ b/examples/HydroTests/Diffusion_1D/makeIC.py @@ -0,0 +1,143 @@ +""" +This file is part of SWIFT. +Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +Creates initial conditiosn for the ContactDiscontinuty_1D test. +Requires the swiftsimio library. +""" + +import numpy as np +import unyt + + +def get_particle_positions(box_length: float, n_part: int) -> np.array: + """ + Gets the particle positions evenly spaced along a _periodic_ box. + """ + + dx = box_length / float(n_part) + x = np.arange(n_part, dtype=float) * dx + (0.5 * dx) + + return x + + +def get_particle_u(low: float, high: float, n_part: int) -> np.array: + """ + Gets the particle internal energies, which alternate between low + and high. Make sure n_part is even. + """ + + indicies = np.arange(n_part) + u = unyt.unyt_array(np.empty(n_part, dtype=float), low.units) + u[...] = low + u[(indicies % 2).astype(bool)] = high + + return u + + +def get_particle_masses(mass: float, n_part: int) -> np.array: + """ + Gets the particle masses. + """ + + m = unyt.unyt_array(np.empty(n_part, dtype=float), mass.units) + m[...] = mass + + return m + + +def get_particle_hsml(box_length: float, n_part: int) -> np.array: + """ + Gets the particle smoothing lengths based on their MIPS. + """ + + mips = box_length / float(n_part) + hsml = unyt.unyt_array(np.empty(n_part, dtype=float), box_length.units) + hsml[...] = mips + + return hsml + + +if __name__ == "__main__": + import argparse as ap + from swiftsimio import Writer + + parser = ap.ArgumentParser( + description="Makes initial conditions for the ContactDiscontinuity_1D test." + ) + + parser.add_argument( + "-n", + "--npart", + help="Number of particles in the box. Make sure this is even. Default: 900", + type=int, + default=900, + ) + + parser.add_argument( + "-m", + "--mass", + help="Particle mass in grams. Default: 1e-4", + type=float, + default=1e-4, + ) + + parser.add_argument( + "-l", + "--low", + help="Low value for the internal energy (in ergs/g). Default: 1e-6", + type=float, + default=1e-6, + ) + + parser.add_argument( + "-t", + "--high", + help="Top/high value for the internal energy (in ergs/g). Default: 1e-4", + type=float, + default=1e-4, + ) + + parser.add_argument( + "-b", "--boxsize", help="Boxsize in cm. Default: 1.0", type=float, default=1.0 + ) + + args = vars(parser.parse_args()) + boxsize = args["boxsize"] * unyt.cm + n_part = args["npart"] + mass = args["mass"] * unyt.g + low = args["low"] * unyt.erg / unyt.g + high = args["high"] * unyt.erg / unyt.g + + if not (n_part % 2 == 0): + raise AttributeError("Please ensure --npart is even.") + + cgs = unyt.unit_systems.cgs_unit_system + + boxsize = 1.0 * unyt.cm + + writer = Writer(cgs, boxsize, dimension=1) + + coordinates = np.zeros((n_part, 3), dtype=float) * unyt.cm + coordinates[:, 0] = get_particle_positions(boxsize, n_part) + + writer.gas.coordinates = coordinates + writer.gas.velocities = np.zeros((n_part, 3), dtype=float) * unyt.cm / unyt.s + writer.gas.masses = get_particle_masses(mass, n_part) + writer.gas.internal_energy = get_particle_u(low, high, n_part) + writer.gas.smoothing_length = get_particle_hsml(boxsize, n_part) + + writer.write("diffusion.hdf5") diff --git a/examples/HydroTests/Diffusion_1D/plotSolution.py b/examples/HydroTests/Diffusion_1D/plotSolution.py new file mode 100644 index 0000000000000000000000000000000000000000..66c8ffc6418f06589a2918ae4d8ed460b0081972 --- /dev/null +++ b/examples/HydroTests/Diffusion_1D/plotSolution.py @@ -0,0 +1,341 @@ +""" +This file is part of SWIFT. +Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +Plots the solution for the ContactDiscontinuity_1D test. +""" + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np + +try: + from scipy.integrate import solve_ivp + solve_ode = True +except: + solve_ode = False + +from swiftsimio import load + +matplotlib.use("Agg") + +def solve_analytic(u_0, u_1, t_0, t_1, alpha=0.1): + """ + Solves the analytic equation: + + $$ + \frac{d \Delta}{d t} = \kappa \alpha ( + \sqrt{u_0 + \Delta} + \sqrt{u_1 + \Delta} + ) ( + u_1 - u_0 - 2 \Delta + ) + $$ + + from time t0 to t1. + + + alpha is the gradient term + + u_0 is the "low" state + + u_1 is the "high" state. + """ + + if not solve_ode: + return [0.0], [0.0] + + def gradient(t, u): + """ + Returns du0/dt, du1/dt + """ + + common = alpha * (np.sqrt(u[0]) + np.sqrt(u[1])) * (u[0] - u[1]) + + return np.array([-1.0 * common, 1.0 * common]) + + ret = solve_ivp(gradient, t_span=[t_0.value, t_1.value], y0=[u_0.value, u_1.value], t_eval=np.linspace(t_0.value, t_1.value, 100)) + + t = ret.t + high = ret.y[1] + low = ret.y[0] + + return t, (high - low) * u_0.units + + +def get_data_dump(metadata): + """ + Gets a big data dump from the SWIFT metadata + """ + + try: + viscosity = metadata.viscosity_info + except: + viscosity = "No info" + + try: + diffusion = metadata.diffusion_info + except: + diffusion = "No info" + + output = ( + "SWIFT\n" + + metadata.code_info + + "\n\n" + + "Compiler\n" + + metadata.compiler_info + + "\n\n" + + "Hydrodynamics\n" + + metadata.hydro_info + + "\n\n" + + "Viscosity\n" + + viscosity + + "\n\n" + + "Diffusion\n" + + diffusion + ) + + return output + + +def get_data_list(start: int, stop: int, handle: str): + """ + Gets a list of swiftsimio objects that contains all of the data. + """ + + data = [load("{}_{:04d}.hdf5".format(handle, x)) for x in range(start, stop + 1)] + + return data + + +def setup_axes(size=[8, 8], dpi=300): + """ + Sets up the axes with the correct labels, etc. + """ + fig, ax = plt.subplots(ncols=2, nrows=2, figsize=size, dpi=dpi) + + ax = ax.flatten() + + ax[0].axis("off") + ax[1].set_xlabel("Time [s]") + ax[1].set_ylabel("Relative energy difference $u/\\left<u\\right>$") + + ax[2].set_xlabel("Time [s]") + ax[2].set_ylabel("Deviation in position relative to MIPS [$\\times 10^{6}$]") + + ax[3].set_xlabel("Time [s]") + + return fig, ax + +def mean_std_max_min(data): + """ + Returns: + mean, stdev, max, min + for your data. + """ + means = np.array([np.mean(x) for x in data]) + stdevs = np.array([np.std(x) for x in data]) + maxs = np.array([np.max(x) for x in data]) + mins = np.array([np.min(x) for x in data]) + + return means, stdevs, maxs, mins + + +def extract_plottables_u(data_list): + """ + Extracts the plottables for the internal energies. Returns: + mean, stdev, max, min differences between adjacent internal energies + """ + + data = [ + np.diff(x.gas.internal_energy.value) / np.mean(x.gas.internal_energy.value) + for x in data_list + ] + + return mean_std_max_min(data) + + +def extract_plottables_x(data_list): + """ + Extracts the plottables for positions. Returns: + mean, stdev, max, min * 1e6 deviations from original position + """ + + n_part = data_list[0].metadata.n_gas + boxsize = data_list[0].metadata.boxsize[0].value + dx = boxsize / n_part + + original_x = np.arange(n_part, dtype=float) * dx + (0.5 * dx) + + deviations = [1e6 * abs(original_x - x.gas.coordinates.value[:, 0]) / dx for x in data_list] + + return mean_std_max_min(deviations) + + +def extract_plottables_rho(data_list): + """ + Extracts the plottables for pressure. Returns: + mean, stdev, max, min * 1e6 deviations from mean density + """ + + P = [x.gas.density.value for x in data_list] + mean_P = [np.mean(x) for x in P] + deviations = [1e6 * (x - y) / x for x, y in zip(mean_P, P)] + + return mean_std_max_min(deviations) + + +def extract_plottables_diff(data_list): + """ + Extracts the plottables for pressure. Returns: + mean, stdev, max, min * 1e6 deviations from mean density + """ + + P = [x.gas.diffusion.value for x in data_list] + + return mean_std_max_min(P) + + +def make_plot(start: int, stop: int, handle: str): + """ + Makes the plot and returns the figure and axes objects. + """ + fig, ax = setup_axes() + data_list = get_data_list(start, stop, handle) + data_dump = get_data_dump(data_list[0].metadata) + t = [x.metadata.t for x in data_list] + means, stdevs, maxs, mins = extract_plottables_u(data_list) + x_means, x_stdevs, x_maxs, x_mins = extract_plottables_x(data_list) + + try: + alpha = np.mean([np.mean(x.gas.diffusion) for x in data_list]) + except AttributeError: + # Must be using a non-diffusive scheme. + alpha = 0.0 + + ax[0].text( + 0.5, + 0.5, + data_dump, + ha="center", + va="center", + fontsize=8, + transform=ax[0].transAxes, + ) + + ax[1].fill_between( + t, means - stdevs, means + stdevs, color="C0", alpha=0.5, edgecolor="none" + ) + ax[1].plot(t, means, label="Mean", c="C0") + ax[1].plot(t, maxs, label="Max", linestyle="dashed", c="C1") + ax[1].plot(t, mins, label="Min", linestyle="dashed", c="C2") + + if solve_ode: + times_ode, diff = solve_analytic( + u_0=data_list[0].gas.internal_energy.min(), + u_1=data_list[0].gas.internal_energy.max(), + t_0=t[0], + t_1=t[-1], + alpha=( + np.sqrt(5.0/3.0 * (5.0/3.0 - 1.0)) * + alpha / data_list[0].gas.smoothing_length[0].value + ) + ) + + ax[1].plot( + times_ode, + (diff) / np.mean(data_list[0].gas.internal_energy), + label="Analytic", + linestyle="dotted", + c="C3" + ) + + #import pdb;pdb.set_trace() + + ax[2].fill_between( + t, x_means - x_stdevs, x_means + x_stdevs, color="C0", alpha=0.5, edgecolor="none" + ) + ax[2].plot(t, x_means, label="Mean", c="C0") + ax[2].plot(t, x_maxs, label="Max", linestyle="dashed", c="C1") + ax[2].plot(t, x_mins, label="Min", linestyle="dashed", c="C2") + + try: + # Give diffusion info a go; this may not be present + diff_means, diff_stdevs, diff_maxs, diff_mins = extract_plottables_diff(data_list) + + ax[3].set_ylabel(r"Diffusion parameter $\alpha_{diff}$") + ax[3].fill_between( + t, diff_means - diff_stdevs, diff_means + diff_stdevs, color="C0", alpha=0.5, edgecolor="none" + ) + ax[3].plot(t, diff_means, label="Mean", c="C0") + ax[3].plot(t, diff_maxs, label="Max", linestyle="dashed", c="C1") + ax[3].plot(t, diff_mins, label="Min", linestyle="dashed", c="C2") + + except: + # Diffusion info must not be present. + rho_means, rho_stdevs, rho_maxs, rho_mins = extract_plottables_rho(data_list) + + ax[3].set_ylabel("Deviation from mean density $(\\rho_i - \\bar{\\rho}) / \\bar{\\rho}$ [$\\times 10^{6}$]") + ax[3].fill_between( + t, rho_means - rho_stdevs, rho_means + rho_stdevs, color="C0", alpha=0.5, edgecolor="none" + ) + ax[3].plot(t, rho_means, label="Mean", c="C0") + ax[3].plot(t, rho_maxs, label="Max", linestyle="dashed", c="C1") + ax[3].plot(t, rho_mins, label="Min", linestyle="dashed", c="C2") + + ax[1].legend(loc=1, markerfirst=False) + + ax[1].set_xlim(t[0], t[-1]) + ax[2].set_xlim(t[0], t[-1]) + ax[3].set_xlim(t[0], t[-1]) + + fig.tight_layout() + + return fig, ax + + +if __name__ == "__main__": + import argparse as ap + + parser = ap.ArgumentParser( + description="Makes a plot of the data from the ContactDiscontinuity_1D test." + ) + + parser.add_argument( + "-i", "--initial", help="Initial snapshot. Default: 0", type=int, default=0 + ) + + parser.add_argument( + "-f", "--final", help="Final snapshot. Default: 50", type=int, default=50 + ) + + parser.add_argument( + "-s", + "--snapshot", + help="First part of the snapshot filename. Default: diffusion", + type=str, + default="diffusion", + ) + + parser.add_argument( + "-o", + "--output", + help="Output filename. Default: diffusion.png", + type=str, + default="diffusion.png", + ) + + args = vars(parser.parse_args()) + + fig, ax = make_plot(args["initial"], args["final"], args["snapshot"]) + + fig.savefig(args["output"]) diff --git a/examples/HydroTests/Diffusion_1D/run.sh b/examples/HydroTests/Diffusion_1D/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..fb70805ca01b96df20fff438b2141b03ccf6e7ab --- /dev/null +++ b/examples/HydroTests/Diffusion_1D/run.sh @@ -0,0 +1,11 @@ +#!/bin/bash + + # Generate the initial conditions if they are not present. +if [ ! -e diffusion.hdf5 ] +then + echo "Generating initial conditions for the Sedov blast example..." + python makeIC.py +fi + +# Run SWIFT +../../swift --hydro --limiter --threads=1 diffusion.yml 2>&1 | tee output.log diff --git a/examples/HydroTests/Diffusion_1D/run_set.sh b/examples/HydroTests/Diffusion_1D/run_set.sh new file mode 100644 index 0000000000000000000000000000000000000000..d6b1f233e65b4e7851f15cf3bc8aadceff4710d7 --- /dev/null +++ b/examples/HydroTests/Diffusion_1D/run_set.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +beta=(1.0 0.1 0.01 0.001) +swift_location="../../swift" +flags="--hydro --limiter --threads=2" +parameter="diffusion.yml" +parameter_fixed="diffusion_fixed_alpha.yml" +make_ic_script="makeIC.py" +plot_script="plotSolution.py" + +for i in ${beta[@]}; +do + mkdir beta_$i + cd beta_$i + + python ../$make_ic_script + + ../$swift_location $flags -P "SPH:diffusion_beta:${i}" ../$parameter + python ../$plot_script + ../$swift_location $flags -P "SPH:diffusion_beta:${i}" ../$parameter_fixed + python ../$plot_script -s diffusion_fixed_alpha -o diffusion_fixed_alpha.png + + rm *.hdf5 + + cd .. +done diff --git a/src/hydro/AnarchyPU/hydro.h b/src/hydro/AnarchyPU/hydro.h index 9bb53f290acb16b4f9efc44e430a78a6d1f5c5ff..77c914c2b9eda678dcc4f9c41518b17938b26e1d 100644 --- a/src/hydro/AnarchyPU/hydro.h +++ b/src/hydro/AnarchyPU/hydro.h @@ -693,11 +693,18 @@ __attribute__((always_inline)) INLINE static void hydro_prepare_force( const float sqrt_u = sqrtf(p->u); /* Calculate initial value of alpha dt before bounding */ /* alpha_diff_dt is cosmology-less */ + /* Evolution term: following Schaller+ 2015 */ float alpha_diff_dt = hydro_props->diffusion.beta * p->h * p->diffusion.laplace_u / sqrt_u; + /* Decay term: not documented in Schaller+ 2015 but was present + * in the original EAGLE code and in Schaye+ 2015 */ + alpha_diff_dt -= + (p->diffusion.alpha - hydro_props->diffusion.alpha_min) / tau; - float new_diffusion_alpha = p->diffusion.alpha + alpha_diff_dt * dt_alpha; + float new_diffusion_alpha = p->diffusion.alpha; + new_diffusion_alpha += alpha_diff_dt * dt_alpha; + /* Consistency checks to ensure min < alpha < max */ if (new_diffusion_alpha > hydro_props->diffusion.alpha_max) { new_diffusion_alpha = hydro_props->diffusion.alpha_max; } else if (new_diffusion_alpha < hydro_props->diffusion.alpha_min) { diff --git a/src/hydro_properties.c b/src/hydro_properties.c index f14c88bfb5128c1da17590f50698e5d038734b71..ff578aec139ad06cb80279d8d827a41975f9773f 100644 --- a/src/hydro_properties.c +++ b/src/hydro_properties.c @@ -317,6 +317,12 @@ void hydro_props_print_snapshot(hid_t h_grpsph, const struct hydro_props *p) { io_write_attribute_f(h_grpsph, "Beta viscosity", const_viscosity_beta); io_write_attribute_f(h_grpsph, "Max v_sig ratio (limiter)", const_limiter_max_v_sig_ratio); + io_write_attribute_f(h_grpsph, "Diffusion alpha", p->diffusion.alpha); + io_write_attribute_f(h_grpsph, "Diffusion alpha (max)", + p->diffusion.alpha_max); + io_write_attribute_f(h_grpsph, "Diffusion alpha (min)", + p->diffusion.alpha_min); + io_write_attribute_f(h_grpsph, "Diffusion beta", p->diffusion.beta); } #endif