diff --git a/configure b/configure index 2330763..e852f55 100755 --- a/configure +++ b/configure @@ -5,7 +5,6 @@ cat > src/app_compile_opts.hpp << EOF #define MARA_COMPILE_SUBPROGRAM_PARTDOM 1 #define MARA_COMPILE_SUBPROGRAM_CLOUD 1 #define MARA_COMPILE_SUBPROGRAM_SEDOV 1 -#define MARA_COMPILE_SUBPROGRAM_BINARY_V1 1 #define MARA_COMPILE_SUBPROGRAM_BINARY 1 #define MARA_COMPILE_SUBPROGRAM_AMRSAND 1 #define MARA_COMPILE_SUBPROGRAM_TEST 1 diff --git a/examples/euler_1d/README.md b/examples/euler_1d/README.md new file mode 100644 index 0000000..62eb231 --- /dev/null +++ b/examples/euler_1d/README.md @@ -0,0 +1,32 @@ +# 1-D EULER CODE + +Simulate a shock traveling on a 1-D grid. After creating the output files for a range of resolutions, the diffusion of the shock is quantified. + +## Getting Started + +After compiling Mara3 (check the README in the root directory for instructions), the executable: + +examples/euler_1d/euler_1d + +should exist. Run a suite of parameter settings by opening a terminal, changing into the root directory (Mara3) +and running: + +./tools/run_suite.py examples/euler_1d/vary_resolution.py --submit + +This will take a few seconds. Upon completion, this folder should contain .hdf5 files with output at various resolutions: + +examples/euler_1d/vary_resolution/res*/diagnostics*.h5 + +Then open the jupyter notebook: euler_1d.ipynb and run, creating plots. + +### Prerequisites + +Python 3.5.4 and standard modules. + +## Authors + +* **Magdalena Siwek** + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/examples/euler_1d/euler_1d.cpp b/examples/euler_1d/euler_1d.cpp new file mode 100644 index 0000000..a11031d --- /dev/null +++ b/examples/euler_1d/euler_1d.cpp @@ -0,0 +1,284 @@ +/** + ============================================================================== + Copyright 2019, Magdalena Siwek and Jonathan Zrake + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + ============================================================================== +*/ + + + + +#include +#include +#include +#include +#include +#include "core_ndarray.hpp" +#include "core_ndarray_ops.hpp" +#include "core_hdf5.hpp" +#include "app_config.hpp" +#include "app_filesystem.hpp" +#include "app_serialize.hpp" +#include "physics_euler.hpp" + + + + + +/** + * @brief A data structure that stores the state of the whole simulation. + */ +struct state_t +{ + int iteration; + double time; + nd::shared_array vertices; + nd::shared_array conserved; + int diagnostics_count; + double diagnostics_last; +}; + + + + +/** + * @brief A data structure holding numerical parameters used by the solver. + */ +struct solver_data_t +{ + double cfl_number; + double pulse_width; + double gamma; +}; + + + + +/** + * @brief Create the solver data. + * + * @param[in] run_config The run configuration to use + * + * @return The solver data + */ +solver_data_t create_solver_data(mara::config_t run_config) +{ + return { + run_config.get_double("cfl_number"), + run_config.get_double("pulse_width"), + run_config.get_double("gamma"), + }; +} + + + + +/** + * @brief Create an initial simulation state. + * + * @param[in] run_config The run configuration to use + * + * @return The initial state + */ +state_t create_initial_state(mara::config_t run_config, const solver_data_t& solver_data) +{ + auto nc = run_config.get_int("resolution"); + auto cd = run_config.get_double("cd"); + auto rhoL = run_config.get_double("rhoL"); + auto rhoR = run_config.get_double("rhoR"); + auto pL = run_config.get_double("pL"); + auto pR = run_config.get_double("pR"); + auto gamma = run_config.get_double("gamma"); + + auto xv = nd::linspace(-1, 1, nc + 1); + auto xc = xv | nd::midpoint_on_axis(0); + + auto Pl = mara::euler::primitive_t() + .with_mass_density(rhoL) + .with_velocity_1(0.0) + .with_gas_pressure(pL); + + auto Pr = mara::euler::primitive_t() + .with_mass_density(rhoR) + .with_velocity_1(0.0) + .with_gas_pressure(pR); + + auto primitive = xc | nd::map([Pl, Pr, cd] (auto x) { return x < cd ? Pl : Pr; }); + auto conserved = primitive | nd::map([gamma] (auto p) { return p.to_conserved_density(gamma); }); + + return{ + 0, // iteration + 0.0, // time + xv.shared(), + conserved.shared(), + 0, // diagnostics_count + 0.0 // diagnostics_last + }; +} + + + + +/** + * @brief Return the next state of the simulation. + * + * @param[in] state The state at time t + * @param[in] solver_data The solver data to use + * + * @return The state at time t + dt + */ +state_t next(const state_t& state, const solver_data_t& solver_data) +{ + + double gamma = solver_data.gamma; + auto nh = mara::unit_vector_t::on_axis(0); + + auto max_wavespeed_of_primitive = [nh, gamma] (auto p) + { + return std::max( + std::abs(p.wavespeeds(nh, gamma).m.value), + std::abs(p.wavespeeds(nh, gamma).p.value)); + }; + + + //first recover primitive variables + auto primitive = state.conserved | + nd::map([gamma] (auto cons) + { + return mara::euler::recover_primitive(cons, gamma, 0); + }); + + + //maximum wavespeed in the entire domain + auto vmax = primitive + | nd::map(max_wavespeed_of_primitive) + | nd::max(); + + + //spacing between cell vertices + double min_spacing = state.vertices | nd::difference_on_axis(0) | nd::min(); + + + // CFL number, which is set to ensure cells aren't "emptied" in one + // timestep due to poorly chosen dt or grid spacing. This parameter must be + // in the range [0, 1]. + double cfl = solver_data.cfl_number; + auto dt = mara::make_time(cfl * min_spacing / vmax); + auto riemann = [gamma,nh] (auto pl, auto pr) { return mara::euler::riemann_hlle(pl, pr, nh, gamma); }; + auto dx = state.vertices | nd::difference_on_axis(0) | nd::map([] (auto x) { return mara::make_length(x); }); + auto Fhat = primitive | nd::extend_zero_gradient(0) | nd::zip_adjacent2_on_axis(0) | nd::apply(riemann); + auto Fhat_diff = Fhat | nd::difference_on_axis(0); + auto next_conserved = state.conserved - Fhat_diff * dt / dx; + + return { + state.iteration + 1, + state.time + dt.value, + state.vertices, + next_conserved.shared(), + state.diagnostics_count, + state.diagnostics_last, + }; +} + + + + +/** + * @brief Write diagnostic info to a file. + * + * @param[in] state The simulation state + * @param[in] run_config The run config + * + * @return A state, updated to reflect that diagnostics were written + */ +state_t write_diagnostics(const state_t& state, mara::config_t run_config) +{ + auto diagnostics_interval = run_config.get_double("delta_t_diagnostic"); + auto outdir = run_config.get_string("outdir"); + + + if (state.time - state.diagnostics_last >= diagnostics_interval || state.time == 0.0) + { + auto fname = mara::filesystem::join({outdir, mara::create_numbered_filename("diagnostics", state.diagnostics_count, "h5")}); + auto h5f = h5::File(fname, "w"); + auto root = h5f.open_group("/"); + + h5f.write("x", state.vertices | nd::midpoint_on_axis(0) | nd::to_shared()); + h5f.write("t", state.time); + h5f.write("conserved", state.conserved); + mara::write(root, "run_config", run_config); + + auto next_state = state; + next_state.diagnostics_count += 1; + next_state.diagnostics_last += state.time == 0.0 ? 0.0 : diagnostics_interval; + + std::printf("writing %s\n", fname.data()); + return next_state; + } + return state; +} + + + + +/** + * @brief The main function + * + * @param[in] argc The number of arguments from the command line + * @param argv An array of strings with the executable invocation + * + * @return An exit status + */ +int main(int argc, const char* argv[]) +{ + auto cfg_template = mara::make_config_template() + .item("resolution", 1000) + .item("tfinal", 1.0) + .item("outdir", "examples/euler_1d/data") + .item("pulse_width", 1.0) + .item("cfl_number", 0.1) + .item("delta_t_diagnostic", 0.01) + .item("cd", 0.) + .item("rhoL", 1.) + .item("rhoR", 0.1) + .item("pL", 1.) + .item("pR", 0.125) + .item("vL", 0.) + .item("vR", 0.) + .item("gamma", 1.4); + + auto run_config = cfg_template.create().update(mara::argv_to_string_map(argc,argv)); + mara::pretty_print(std::cout, "config", run_config); + mara::filesystem::require_dir(run_config.get_string("outdir")); + + auto solver_data = create_solver_data(run_config); + auto state = create_initial_state(run_config, solver_data); + + while (state.time < run_config.get_double("tfinal")) + { + state = write_diagnostics(state, run_config); + state = next(state, solver_data); + //std::printf("[%04d] t=%lf\n", state.iteration, state.time); + } + write_diagnostics(state, run_config); + + return 0; +} diff --git a/examples/euler_1d/euler_1d.ipynb b/examples/euler_1d/euler_1d.ipynb new file mode 100644 index 0000000..dbc27e2 --- /dev/null +++ b/examples/euler_1d/euler_1d.ipynb @@ -0,0 +1,354 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1-D EULER EQUATIONS\n", + "\n", + "## This is a Jupyter Notebook plotting simulation output generated with Mara3 (https://github.com/jzrake/Mara3). The simulation evolves a 1-D Shock with zero gradient boundary conditions. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 164, + "metadata": {}, + "outputs": [], + "source": [ + "import h5py\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib as mpl\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import glob\n", + "import os\n", + "from exact_riemann import riemann, riemann_f" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot settings " + ] + }, + { + "cell_type": "code", + "execution_count": 196, + "metadata": {}, + "outputs": [], + "source": [ + "# Plotting settings\n", + "mpl.rc('font', **{'family': 'serif', 'sans-serif': ['Times']})\n", + "mpl.rc('lines', solid_capstyle='round')\n", + "mpl.rc('mathtext', fontset='cm')\n", + "plt.rcParams.update({'grid.alpha': 0.5})\n", + "mpl.rcParams['font.size'] = 25" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## In the below we plot the density profile of the shock as it propagates along the grid.\n", + "## We also compute the exact Riemann solution of this problem to contrast with the numerical solution. We plot both to qualitatively see the effects of numerical diffusion and compare for a range of resolutions. In this step, we also compute the L2 error. Plotting of L2 will happen in the last cell.\n", + "\n", + "### The plots, videos and L2 error arrays are stored in figures/res_XXXXX/ " + ] + }, + { + "cell_type": "code", + "execution_count": 166, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vary_resolution/res_01600\n", + "Path already exists: figures/res_01600/\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/magda/gradschool/CCA_summer_school/Mara3/examples/euler_1d/exact_riemann.py:50: RuntimeWarning: divide by zero encountered in true_divide\n", + " xi = (X-x0)/float(T)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vary_resolution/res_00050\n", + "Path already exists: figures/res_00050/\n", + "vary_resolution/res_00800\n", + "Path already exists: figures/res_00800/\n", + "vary_resolution/res_00200\n", + "Path already exists: figures/res_00200/\n", + "vary_resolution/res_00100\n", + "Path already exists: figures/res_00100/\n", + "vary_resolution/res_00400\n", + "Path already exists: figures/res_00400/\n" + ] + } + ], + "source": [ + "a = -1\n", + "b = 1\n", + "x0 = 0\n", + "rhoL = 1.\n", + "rhoR = 0.1\n", + "PL = 1.\n", + "PR = 0.125\n", + "gamma = 1.4\n", + "vR = 0\n", + "vL = 0\n", + "\n", + "all_fps = glob.glob('vary_resolution/*')\n", + "for fp in all_fps:\n", + " print(fp)\n", + " res = fp.split('/')[-1]\n", + " dirname = 'figures/' + res + '/'\n", + " if os.path.exists(dirname):\n", + " print(\"Path already exists: \", dirname)\n", + " if not os.path.exists(dirname):\n", + " print(\"Path doesn't exist, making new filepath: \", dirname)\n", + " os.makedirs(dirname)\n", + "\n", + " for fl in glob.glob(fp + '/*.h5'):\n", + " f = h5py.File(fl)\n", + " density = []\n", + " for j in range(0,len(f['conserved'])):\n", + " density.append(f['conserved'][j][0])\n", + " \n", + " t = f['t'][...]\n", + " N = len(f['conserved'])\n", + " X, rho, v, P = riemann(a, b, x0, N, t, rhoL, vL, PL, rhoR, vR, PR, gamma)\n", + " plt.figure(figsize=(10,7))\n", + " plt.title(res + ', t = %.2f' %t)\n", + " plt.plot(f['x'], density, label='Numerical')\n", + " plt.plot(X, rho, label = 'Exact Riemann')\n", + " plt.legend()\n", + " plt.ylim([0.,1.2])\n", + " plt.savefig(dirname + 'density_plot_%s.png' %fl[-7:-3])\n", + " plt.close()\n", + " \n", + " cmd = 'ffmpeg -r 4 -f image2 -s 1920x1080 -i %sdensity_plot_' %(dirname) + r'%04d.png ' + '-vcodec libx264 -crf 25 -pix_fmt yuv420p %srho_%s.mp4' %(dirname, res)\n", + " os.system(cmd)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Now we plot the numerical and exact density profiles of the shocks side by side for the resolutions tested. They will be put together in a video.\n", + "\n", + "### The plots, videos and L2 error arrays are stored in figures/all_res/rho.mp4 " + ] + }, + { + "cell_type": "code", + "execution_count": 197, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/magda/anaconda2/envs/py3/lib/python3.5/site-packages/matplotlib/cbook/__init__.py:424: MatplotlibDeprecationWarning: \n", + "Passing one of 'on', 'true', 'off', 'false' as a boolean is deprecated; use an actual boolean (True/False) instead.\n", + " warn_deprecated(\"2.2\", \"Passing one of 'on', 'true', 'off', 'false' as a \"\n", + "/Users/magda/gradschool/CCA_summer_school/Mara3/examples/euler_1d/exact_riemann.py:50: RuntimeWarning: divide by zero encountered in true_divide\n", + " xi = (X-x0)/float(T)\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 197, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = -1\n", + "b = 1\n", + "x0 = 0\n", + "rhoL = 1.\n", + "rhoR = 0.1\n", + "PL = 1.\n", + "PR = 0.125\n", + "gamma = 1.4\n", + "vR = 0\n", + "vL = 0\n", + "\n", + "all_fps = glob.glob('vary_resolution/res_*')\n", + "all_res = sorted([int(j.split('/')[-1][4:]) for j in all_fps])\n", + "\n", + "dirname = 'figures/all_res'\n", + "\n", + "if os.path.exists(dirname):\n", + " print(\"Path already exists: \", dirname)\n", + "if not os.path.exists(dirname):\n", + " print(\"Path doesn't exist, making new filepath: \", dirname)\n", + " os.makedirs(dirname)\n", + "\n", + "for i in range(0,51):\n", + " \n", + " fig = plt.figure(figsize=(25,20))\n", + " ax = fig.add_subplot(111) \n", + " ax.spines['top'].set_color('none')\n", + " ax.spines['bottom'].set_color('none')\n", + " ax.spines['left'].set_color('none')\n", + " ax.spines['right'].set_color('none')\n", + " ax.tick_params(labelcolor='w', top='off', bottom='off', left='off', right='off')\n", + " n = 1\n", + " for res in all_res:\n", + " fp = 'vary_resolution/res_%05d/diagnostics.%04d.h5' %(res,i)\n", + " f = h5py.File(fp)\n", + " density = []\n", + " for j in range(0,len(f['conserved'])):\n", + " density.append(f['conserved'][j][0])\n", + "\n", + " t = f['t'][...]\n", + " N = len(f['conserved'])\n", + " X, rho, v, P = riemann(a, b, x0, N, t, rhoL, vL, PL, rhoR, vR, PR, gamma)\n", + " \n", + " fig.add_subplot(int(np.ceil(len(all_res)/2.)),2,n)\n", + " plt.title(\"N = %d\" %res)\n", + " plt.plot(f['x'], density, label='Numerical')\n", + " plt.plot(X, rho, label = 'Exact Riemann')\n", + " plt.legend()\n", + " plt.ylim([0.,1.2])\n", + "\n", + " n+=1\n", + " \n", + " ax.set_xlabel('x')\n", + " ax.set_ylabel(r'$\\rho$')\n", + " plt.tight_layout()\n", + " plt.savefig(dirname + '/rho_%04d.png' %i)\n", + " plt.close()\n", + "\n", + "cmd = 'ffmpeg -r 4 -f image2 -s 1920x1080 -i %s/rho_' %dirname + r'%04d.png ' + '-vcodec libx264 -crf 25 -pix_fmt yuv420p %s/rho.mp4' %dirname\n", + "os.system(cmd)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Finally we look at the L2 error as a function of resolution." + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'L2')" + ] + }, + "execution_count": 176, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def L2(numerical, exact, dx):\n", + " return np.sum((numerical-exact)**2 * dx)\n", + "\n", + "a = -1\n", + "b = 1\n", + "x0 = 0\n", + "rhoL = 1.\n", + "rhoR = 0.1\n", + "PL = 1.\n", + "PR = 0.125\n", + "gamma = 1.4\n", + "vR = 0\n", + "vL = 0\n", + "\n", + "#Specify a snapshot to look at:\n", + "i = 25\n", + "\n", + "all_fps = glob.glob('vary_resolution/res_*/*%d.h5' %i)\n", + "\n", + "resolutions = []\n", + "L2_error = []\n", + "\n", + "for fp in all_fps:\n", + " res = fp.split('/')[-2][4:]\n", + " resolutions.append(int(res))\n", + " f = h5py.File(fp)\n", + " \n", + " density_numeric = []\n", + " for j in range(0,len(f['conserved'])):\n", + " density_numeric.append(f['conserved'][j][0])\n", + " dx = f['x'][1] - f['x'][0]\n", + "\n", + " t = f['t'][...]\n", + " N = len(f['conserved'])\n", + " X, density_exact, v, P = riemann(a, b, x0, N, t, rhoL, vL, PL, rhoR, vR, PR, gamma)\n", + " \n", + " L2_error.append(L2(density_numeric, density_exact, dx))\n", + " \n", + "inds = np.argsort(resolutions)\n", + "resolutions = np.array(resolutions)[inds]\n", + "L2_error = np.array(L2_error)[inds]\n", + "\n", + "plt.figure(figsize=(10,7))\n", + "plt.title('L2 Convergence at t = %.2f' %t)\n", + "plt.loglog(resolutions, L2_error, '*', linestyle = '--')\n", + "plt.xlabel(\"Resolution\")\n", + "plt.ylabel(\"L2\")\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/euler_1d/exact_riemann.py b/examples/euler_1d/exact_riemann.py new file mode 100644 index 0000000..81c3101 --- /dev/null +++ b/examples/euler_1d/exact_riemann.py @@ -0,0 +1,130 @@ + +import math +import numpy as np +import matplotlib.pyplot as plt + +def riemann_f(p, rho, v, P, gamma, A, B, cs): + if p <= P: + f = 2*cs*(math.pow(p/P,(gamma-1)/(2*gamma))-1.0) / (gamma-1.0) + df = 2*cs*math.pow(p/P,-(gamma+1)/(2*gamma)) / (2*gamma*P) + else: + f = (p-P)*math.sqrt(A / (p+B)) + df = (1.0 - 0.5*(p-P)/(p+B)) * math.sqrt(A / (p+B)) + return f, df + +def riemann(a, b, x0, N, T, rhoL, vL, PL, rhoR, vR, PR, gamma, + TOL=1.0e-14, MAX=100): + # Returns the solution to the Riemann problem with left state (rhoL,vL,PL), + # and right state (rhoR,vR,PR) after a time 'T' on a grid of N cells on + # [a,b]. The initial discontinuity is placed at x0. + # + # Returns: X, rho, v, P + + AL = 2.0/((gamma+1.0)*rhoL) + AR = 2.0/((gamma+1.0)*rhoR) + BL = (gamma-1.0) / (gamma+1.0) * PL + BR = (gamma-1.0) / (gamma+1.0) * PR + csL = math.sqrt(gamma*PL/rhoL) + csR = math.sqrt(gamma*PR/rhoR) + + p1 = 0.5*(PL + PR) + p = p1 + i = 0 + dp = np.inf + while abs(dp) > abs(p*TOL) and i < MAX: + p = p1 + f1, df1 = riemann_f(p, rhoL, vL, PL, gamma, AL, BL, csL) + f2, df2 = riemann_f(p, rhoR, vR, PR, gamma, AR, BR, csR) + f = f1 + f2 + vR-vL + df = df1 + df2 + + dp = -f/df + p1 = p + dp + i += 1 + + p = p1 + u = 0.5*(vL+vR) + 0.5*(riemann_f(p, rhoR, vR, PR, gamma, AR, BR, csR)[0] + - riemann_f(p, rhoL, vL, PL, gamma, AL, BL, csL)[0]) + + X = a + (b-a)/float(N) * (np.arange(N) + 0.5) + xi = (X-x0)/float(T) + + rho = np.empty(X.shape) + v = np.empty(X.shape) + P = np.empty(X.shape) + + if p > PL: + # Left Shock + rhoLS = rhoL * (p/PL + (gamma-1.0)/(gamma+1.0)) / ( + (gamma-1.0)/(gamma+1.0) * p/PL + 1.0) + SL = vL - csL*math.sqrt(((gamma+1) * p/PL + (gamma-1))/(2*gamma)) + + iL = xi < SL + iLS = (xi >= SL) * (xi < u) + rho[iL] = rhoL + v[iL] = vL + P[iL] = PL + rho[iLS] = rhoLS + v[iLS] = u + P[iLS] = p + else: + # Left Rarefaction + rhoLS = rhoL * math.pow(p/PL, 1.0/gamma) + csLS = csL * math.pow(p/PL, (gamma-1.0) / (2*gamma)) + SHL = vL - csL + STL = u - csLS + + iL = xi < SHL + ifan = (xi >= SHL) * (xi < STL) + iLS = (xi >= STL)*(xi < u) + + rho[iL] = rhoL + v[iL] = vL + P[iL] = PL + rho[ifan] = rhoL * np.power(2.0/(gamma+1) + (gamma-1)/(gamma+1) + * (vL - xi[ifan]) / csL, 2.0/(gamma-1.0)) + v[ifan] = 2.0/(gamma+1) * (csL + 0.5*(gamma-1)*vL + xi[ifan]) + P[ifan] = PL * np.power(2.0/(gamma+1) + (gamma-1)/(gamma+1) + * (vL - xi[ifan]) / csL, 2.0*gamma/(gamma-1.0)) + rho[iLS] = rhoLS + v[iLS] = u + P[iLS] = p + + if p > PR: + # Right Shock + rhoRS = rhoR * (p/PR + (gamma-1.0)/(gamma+1.0)) / ( + (gamma-1.0)/(gamma+1.0) * p/PR + 1.0) + SR = vR + csR*math.sqrt(((gamma+1) * p/PR + (gamma-1))/(2*gamma)) + + iR = xi >= SR + iRS = (xi < SR) * (xi >= u) + rho[iR] = rhoR + v[iR] = vR + P[iR] = PR + rho[iRS] = rhoRS + v[iRS] = u + P[iRS] = p + else: + # Right Rarefaction + rhoRS = rhoR * math.pow(p/PR, 1.0/gamma) + csRS = csR * math.pow(p/PR, (gamma-1.0) / (2*gamma)) + SHR = vR + csR + STR = u + csRS + + iR = xi >= SHR + ifan = (xi < SHR) * (xi >= STR) + iRS = (xi < STR)*(x >= u) + + rho[iR] = rhoR + v[iR] = vR + P[iR] = PR + rho[ifan] = rhoR * np.power(2.0/(gamma+1) - (gamma-1)/(gamma+1) + * (vR - xi[ifan]) / csR, 2.0/(gamma-1.0)) + v[ifan] = 2.0/(gamma+1) * (-csR + 0.5*(gamma-1)*vR + xi[ifan]) + P[ifan] = PR * np.power(2.0/(gamma+1) - (gamma-1)/(gamma+1) + * (vR - xi[ifan]) / csR, 2.0*gamma/(gamma-1.0)) + rho[iRS] = rhoRS + v[iRS] = u + P[iRS] = p + + return X, rho, v, P diff --git a/examples/euler_1d/vary_resolution.py b/examples/euler_1d/vary_resolution.py new file mode 100644 index 0000000..02f7e75 --- /dev/null +++ b/examples/euler_1d/vary_resolution.py @@ -0,0 +1,23 @@ +suite = { + 'subprog': '', + 'comment': 'Variation of resolution', + 'exe': 'examples/euler_1d/euler_1d', + 'job_params': dict(hours=12, nodes=1), + 'mara_opts': dict( + tfinal=0.5, + delta_t_diagnostic=0.01, + cfl_number=0.1, + cd = 0, + rhoL = 1., + rhoR = 0.1, + pL = 1., + pR = 0.125), + 'runs': { + 'res_00050': dict(resolution=50), + 'res_00100': dict(resolution=100), + 'res_00200': dict(resolution=200), + 'res_00400': dict(resolution=400), + 'res_00800': dict(resolution=800), + 'res_01600': dict(resolution=1600), + }, +} diff --git a/math/DiskModels.nb b/math/DiskModels.nb new file mode 100644 index 0000000..ecf0181 --- /dev/null +++ b/math/DiskModels.nb @@ -0,0 +1,960 @@ +(* Content-type: application/vnd.wolfram.mathematica *) + +(*** Wolfram Notebook File ***) +(* http://www.wolfram.com/nb *) + +(* CreatedBy='Mathematica 12.0' *) + +(*CacheID: 234*) +(* Internal cache information: +NotebookFileLineBreakTest +NotebookFileLineBreakTest +NotebookDataPosition[ 158, 7] +NotebookDataLength[ 50024, 950] +NotebookOptionsPosition[ 48869, 923] +NotebookOutlinePosition[ 49206, 938] +CellTagsIndexPosition[ 49163, 935] +WindowFrame->Normal*) + +(* Beginning of Notebook Content *) +Notebook[{ +Cell[BoxData[ + RowBox[{ + RowBox[{"\[Rho]", "[", "r_", "]"}], ":=", + RowBox[{ + RowBox[{"\[Rho]0", " ", + RowBox[{"Exp", "[", + RowBox[{ + RowBox[{"-", + SuperscriptBox[ + RowBox[{"(", + RowBox[{ + RowBox[{"r", "/", "rc"}], "-", "1"}], ")"}], "2"]}], "/", "2"}], + "]"}]}], "+", "\[Rho]1"}]}]], "Input", + CellChangeTimes->{{3.771181253385581*^9, 3.771181293538245*^9}, { + 3.7711826623161383`*^9, 3.7711826624147778`*^9}, {3.7711833393813066`*^9, + 3.771183355061893*^9}, {3.771183456997203*^9, 3.771183469549692*^9}}, + CellLabel->"In[23]:=",ExpressionUUID->"cdadfb99-0250-46ad-99b1-a2ba73a9dbca"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{ + RowBox[{ + SuperscriptBox["cs", "2"], + FractionBox["r", + RowBox[{"\[Rho]", "[", "r", "]"}]], + RowBox[{ + RowBox[{"\[Rho]", "'"}], "[", "r", "]"}]}], "//", + "FullSimplify"}], "\[IndentingNewLine]", + RowBox[{"Plot", "[", + RowBox[{ + RowBox[{ + RowBox[{"{", + RowBox[{"%", ",", + RowBox[{"1", "/", "r"}], ",", + RowBox[{"%", "+", + RowBox[{"1", "/", "r"}]}]}], "}"}], "/.", + RowBox[{"{", + RowBox[{ + RowBox[{"rc", "\[Rule]", "3"}], ",", + RowBox[{"\[Rho]0", "\[Rule]", "1"}], ",", + RowBox[{"\[Rho]1", "\[Rule]", "0.001"}], ",", + RowBox[{"cs", "\[Rule]", "0.05"}]}], "}"}]}], ",", + RowBox[{"{", + RowBox[{"r", ",", "0", ",", "24"}], "}"}]}], "]"}]}], "Input", + CellChangeTimes->{{3.771181296828013*^9, 3.771181310448319*^9}, { + 3.771183369891101*^9, 3.7711833712540894`*^9}, {3.771183477720313*^9, + 3.771183505058072*^9}, {3.771183616512848*^9, 3.771183619716165*^9}, { + 3.771183667621512*^9, 3.7711837039155903`*^9}, {3.771183742851089*^9, + 3.7711838942644653`*^9}, {3.771184007161806*^9, 3.7711840076428843`*^9}, { + 3.7711852689508944`*^9, 3.771185269060916*^9}}, + CellLabel->"In[87]:=",ExpressionUUID->"07096ba1-9598-42af-8320-3e80c8c747e8"], + +Cell[BoxData[ + FractionBox[ + RowBox[{ + SuperscriptBox["cs", "2"], " ", "r", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "r"}], "+", "rc"}], ")"}], " ", "\[Rho]0"}], + RowBox[{ + SuperscriptBox["rc", "2"], " ", + RowBox[{"(", + RowBox[{"\[Rho]0", "+", + RowBox[{ + SuperscriptBox["\[ExponentialE]", + RowBox[{ + FractionBox["1", "2"], " ", + SuperscriptBox[ + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", + FractionBox["r", "rc"]}], ")"}], "2"]}]], " ", "\[Rho]1"}]}], + ")"}]}]]], "Output", + CellChangeTimes->{{3.7711812991439333`*^9, 3.77118131073923*^9}, + 3.771182665714765*^9, 3.771183340622616*^9, 3.7711833716618233`*^9, { + 3.771183460971874*^9, 3.771183505540278*^9}, 3.7711836201341877`*^9, { + 3.771183669298974*^9, 3.7711837042749557`*^9}, {3.771183743312368*^9, + 3.771183894721664*^9}, 3.771184007955426*^9, 3.771185280304861*^9}, + CellLabel->"Out[87]=",ExpressionUUID->"f7dcd4a7-e7c1-4b2f-92cd-26b35afd3cc2"], + +Cell[BoxData[ + GraphicsBox[{{{}, {}, + TagBox[ + {RGBColor[0.368417, 0.506779, 0.709798], AbsoluteThickness[1.6], Opacity[ + 1.], LineBox[CompressedData[" +1:eJwVlnc8le8bx8ksyiokK/vY5xznHFn3ZSYkDcooJSRkhYz6Zid7ZDtKC4lU +lNZzZxRSRkMaSlaSMpIZfuf3zzmvz+vcr8/9ud7XdT3n2ermv8djDRsbWwPr +4//f89Z+TDv/S0YL1R9/V7D9M0xwV1bpVPoPpRK1Gs0XPxi9+j3knaqUhu4Q +8o8C0n8YTd41TfFVKkFpjgnXpPwXjGJE/0i6KFWjx5+g9pLmWtTeYjW8V4lA +cU9cX9ZXiSP/S6Ftu5Q6EJn9m8BopTLSrR1UihHrRJJZIkvlB1QQm7uKiRNn +FwKH+DVenCTUdirnYsrfLsS33eZI/wFV5HTR32nq/WukVbFi8PCvOjr9W77r +YXEPSu4eqU1c1UYNKSmPdip9Qc/Jf5blymmIGPvwkq/iC/qUWCmwfYaGHlsq +f2lT+4pi9iVGvUF05LzmCNsV8X4kxrHnjOI7OrInluRkpL+hmazh11XzDGRF +J3uJkQbRGwm/gF3yeshy+H1AqPcgshzYrr3goYe2X/gv/F3lILKm+1jwlesh +s+n2pGzNISTgGHfSS1UfGVV7VgnoDCPlrn+N3CoGiKpUPM2DviOyVNOL19xG +iPLOZMkz6ju6Oxxayw5GSDvuB8fzhu9oXO7NeH6YEdIcoG+KMxtFReNN29lG +jZBKSTeDzeoHSm2OKLyDEZIU5Tkzv+8n8nmytXJPBSCJZ1Xx+3N/ovJgjtPq +DYA2B+9Lu/f+J6oR+BPwsBeQ6OvSi8FO46jshv7zaW5jJJhm0Djp+gvFvbTa +/M7VGHFxB/H88J5AvwyubjvMYYJGd5wV+FM2gUyOPHTV22SCXqSmiC0PTaD6 +S2sOv1YyQS1Jb7ITtSaREvvV3/o7TFCy8O8rxRWTyG0hB/4mmaDTtTNlW1sn +kbP7dR2i0AT5OSxVXh+ZRMK6I+zmN0yQXSFvbY38FMosCVNOaTVBG+Xkm5uZ +U+jjh/witjWmiKuZ1Lrj8RTi6X1tXy9gimY9tF92fJxCVTw7RzWkTFFvheHb +XrFppD+RnrOHYYqY5AND45nTyCrSeEOMlylKe3NoNLBmGkkU+J5TCTZFUSEe +47Md0+iWytL7lLOmyO1B0Awb/x/UFvthJS3HFCkZp3JuSviDdha+OBxOmCKx +wWzewqt/kGubUdaGVlPEG1/IL9P0BwmTrQuPdJuisdayjSS2GZQm+FLDeNAU +ffKuFq+WnkGB/YIPXv00RS/56ySphjPoYH6ahPiMKbpl16hgGDGD7hr2uo5w +mKFL060qjfkz6FlSqKk3nxnKutCpvv3+DPL8b/pfpbAZCu79rLN7ZgbteKs6 +5SVrhjwiBnV7hP+iDZmP1IaUzJCD5JiBM/kvuln+Wl9KwwzpHp4z8/D7iwws +0edWXTNEWrNiOZbyF7ls9vHVNzJDElc5d/pX/kWzPW3dPqZm6N93Ifvw0b8o +2qxzC/dOM/TrvLjjCvcsSpO1ZQvYbYa+qMkcjFWcRUVQ8yDP3gw99Vf3SHWb +Rf1a3TdkD5qh20LU48LRs6hvjfdA9GEzdPnuthN5F2dRnWzh70tHzVC2PQRK +ErNofsuuzjOeZihuziKk9PMsingaGid+3AyFFOwMV1qaRSvCq+sDfcyQp/6+ +M5Wb59B3ux6fcyfM0IE+p2ht3Tk0OjHGdPY3QzvOHomvc5hDVUy1q78CzBAl +vcGiNHgO3X78OYkRZIYkL27lTc2aQ7cib7w3PmmGuG9Ft4XVzCEV+fgk/mAz +NEl8S3LvmENI8+CVPJb+2GFsYzc+hw5ZycsNsnTTl9L1BuvmEdv+Vu4Jlr75 +m71TWWUeHfMw3P2IpXNXjmSIWMwjp/qT7JYsHbWhcffq0Xn0tclFLId1n7e0 +nMjP6Hm0qj6WdZmVZ59mzNuei/Pov7b1wQGBZsjQaCCn8ck8Kr344OEKqx4l +W5P91Z/m0bfhr547/cyQ4KHL4oUL8+jpmpNhLr5maPHEmo/xYgsoJMVjQs3b +DA2dcSsKpC0gbHmj/dExM9SR2uhycO8CylfXWc/vYYbqmXLSOwIXEJ/L7F1J +N1Y/qmK+6qQvoG+kIfzrkBlKfjJwSbZqAf0nNqV91pk1X69M3PjbF1DGW0Ge +7v1m6FDfZfn50QU0aMQwGdzL4rvsdr1TYRFVnfL6bmnN4ru+6dgjk0WkP+K8 +u9iCxVdKnlR2eBG1GCvL1hiboQ8Gg5X/FS8i5qT4uDjDDDXamJ7wfriIeG1I +P06QWTxdrmg69C6iHVeHrWLVWPxOH72tsXEJfTbRNZiQZvFLaQraTFlCZdMP +2s3FWfyK5XW47JZQXyCBXYTMkPLjwXufk5fQVLrKu/Y1rPqXjj5O5viHOFJO +1VYPmKJ6vuYzp7b+Q+dbOI/xfjRFl7cooKPoH/JIfXFGjrWvIfpDDXqR/9CP ++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 + "]], LineBox[CompressedData[" +1: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 + "]], + LineBox[CompressedData[" +1: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 +72qa/vuXhfW/FTfY27PhfzEcWTI= + "]]}, + Annotation[#, "Charting`Private`Tag$5951#1"]& ]}, {}}, + AspectRatio->NCache[GoldenRatio^(-1), 0.6180339887498948], + Axes->{True, True}, + AxesLabel->{None, None}, + AxesOrigin->{0, 0}, + DisplayFunction->Identity, + Frame->{{False, False}, {False, False}}, + FrameLabel->{{None, None}, {None, None}}, + FrameTicks->{{Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}, {Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}}, + GridLines->{None, None}, + GridLinesStyle->Directive[ + GrayLevel[0.5, 0.4]], + ImagePadding->All, + Method->{ + "DefaultBoundaryStyle" -> Automatic, + "DefaultGraphicsInteraction" -> { + "Version" -> 1.2, "TrackMousePosition" -> {True, False}, + "Effects" -> { + "Highlight" -> {"ratio" -> 2}, "HighlightPoint" -> {"ratio" -> 2}, + "Droplines" -> { + "freeformCursorMode" -> True, + "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> + AbsolutePointSize[6], "ScalingFunctions" -> None, + "CoordinatesToolOptions" -> {"DisplayFunction" -> ({ + (Identity[#]& )[ + Part[#, 1]], + (Identity[#]& )[ + Part[#, 2]]}& ), "CopiedValueFunction" -> ({ + (Identity[#]& )[ + Part[#, 1]], + (Identity[#]& )[ + Part[#, 2]]}& )}}, + PlotRange->{{0, 24}, {-0.02885808580904766, 0.3935039979642211}}, + PlotRangeClipping->True, + PlotRangePadding->{{ + Scaled[0.02], + Scaled[0.02]}, { + Scaled[0.05], + Scaled[0.05]}}, + Ticks->{Automatic, Automatic}]], "Output", + CellChangeTimes->{{3.7711812991439333`*^9, 3.77118131073923*^9}, + 3.771182665714765*^9, 3.771183340622616*^9, 3.7711833716618233`*^9, { + 3.771183460971874*^9, 3.771183505540278*^9}, 3.7711836201341877`*^9, { + 3.771183669298974*^9, 3.7711837042749557`*^9}, {3.771183743312368*^9, + 3.771183894721664*^9}, 3.771184007955426*^9, 3.771185280403516*^9}, + CellLabel->"Out[88]=",ExpressionUUID->"72934ea7-1ec7-47fb-bd5c-ac8822645804"] +}, Open ]], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{"Plot", "[", + RowBox[{ + RowBox[{ + RowBox[{"\[Rho]", "[", "r", "]"}], "/.", + RowBox[{"{", + RowBox[{ + RowBox[{"rc", "\[Rule]", "3"}], ",", + RowBox[{"\[Rho]0", "\[Rule]", "1"}], ",", + RowBox[{"\[Rho]1", "\[Rule]", "0.0001"}], ",", + RowBox[{"cs", "\[Rule]", "0.05"}]}], "}"}]}], ",", + RowBox[{"{", + RowBox[{"r", ",", "0", ",", "24"}], "}"}]}], "]"}]], "Input", + CellChangeTimes->{{3.771183727537743*^9, 3.771183756135447*^9}, { + 3.771183850897531*^9, 3.771183866937475*^9}, {3.7711852713408327`*^9, + 3.771185275045209*^9}}, + CellLabel->"In[86]:=",ExpressionUUID->"ad755b77-3f52-4d4e-9ccb-49dccd64a83d"], + +Cell[BoxData[ + GraphicsBox[{{{}, {}, + TagBox[ + {RGBColor[0.368417, 0.506779, 0.709798], AbsoluteThickness[1.6], Opacity[ + 1.], LineBox[CompressedData[" +1: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 +ToMXew== + "]]}, + Annotation[#, "Charting`Private`Tag$5748#1"]& ]}, {}}, + AspectRatio->NCache[GoldenRatio^(-1), 0.6180339887498948], + Axes->{True, True}, + AxesLabel->{None, None}, + AxesOrigin->{0, 0}, + DisplayFunction->Identity, + Frame->{{False, False}, {False, False}}, + FrameLabel->{{None, None}, {None, None}}, + FrameTicks->{{Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}, {Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}}, + GridLines->{None, None}, + GridLinesStyle->Directive[ + GrayLevel[0.5, 0.4]], + ImagePadding->All, + Method->{ + "DefaultBoundaryStyle" -> Automatic, + "DefaultGraphicsInteraction" -> { + "Version" -> 1.2, "TrackMousePosition" -> {True, False}, + "Effects" -> { + "Highlight" -> {"ratio" -> 2}, "HighlightPoint" -> {"ratio" -> 2}, + "Droplines" -> { + "freeformCursorMode" -> True, + "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> + AbsolutePointSize[6], "ScalingFunctions" -> None, + "CoordinatesToolOptions" -> {"DisplayFunction" -> ({ + (Identity[#]& )[ + Part[#, 1]], + (Identity[#]& )[ + Part[#, 2]]}& ), "CopiedValueFunction" -> ({ + (Identity[#]& )[ + Part[#, 1]], + (Identity[#]& )[ + Part[#, 2]]}& )}}, + PlotRange->{{0, 24}, {0., 1.0000995670665151`}}, + PlotRangeClipping->True, + PlotRangePadding->{{ + Scaled[0.02], + Scaled[0.02]}, { + Scaled[0.05], + Scaled[0.05]}}, + Ticks->{Automatic, Automatic}]], "Output", + CellChangeTimes->{{3.771183730571362*^9, 3.7711837564745607`*^9}, { + 3.771183852089654*^9, 3.771183867233349*^9}, {3.7711852718884563`*^9, + 3.7711852753650084`*^9}}, + CellLabel->"Out[86]=",ExpressionUUID->"c1b74a19-90be-43e3-93be-8eb3dd0e5619"] +}, Open ]], + +Cell[BoxData[""], "Input", + CellChangeTimes->{{3.771184035398038*^9, + 3.771184069214901*^9}},ExpressionUUID->"e67d289e-56b6-43c5-b72b-\ +9dbd4db072d9"], + +Cell[CellGroupData[{ + +Cell[BoxData[ + RowBox[{ + RowBox[{ + FractionBox[ + RowBox[{ + SuperscriptBox["cs", "2"], " ", "r", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "r"}], "+", "rc"}], ")"}], " ", + RowBox[{"(", + RowBox[{ + RowBox[{"\[Rho]", "[", "r", "]"}], "-", "\[Rho]1"}], ")"}]}], + RowBox[{ + SuperscriptBox["rc", "2"], " ", + RowBox[{"\[Rho]", "[", "r", "]"}]}]], "\[Equal]", + FractionBox[ + RowBox[{ + SuperscriptBox["cs", "2"], " ", "r", " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "r"}], "+", "rc"}], ")"}], " ", "\[Rho]0"}], + RowBox[{ + SuperscriptBox["rc", "2"], " ", + RowBox[{"(", + RowBox[{"\[Rho]0", "+", + RowBox[{ + SuperscriptBox["\[ExponentialE]", + RowBox[{ + FractionBox["1", "2"], " ", + SuperscriptBox[ + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", + FractionBox["r", "rc"]}], ")"}], "2"]}]], " ", "\[Rho]1"}]}], + ")"}]}]]}], "//", "FullSimplify"}]], "Input", + CellChangeTimes->{{3.771184087273678*^9, 3.7711841634120903`*^9}}, + CellLabel->"In[84]:=",ExpressionUUID->"2feb4617-4791-4722-bd63-ffbe8607ff19"], + +Cell[BoxData["True"], "Output", + CellChangeTimes->{{3.7711840988545103`*^9, 3.7711841638809853`*^9}}, + CellLabel->"Out[84]=",ExpressionUUID->"8c45a434-466f-4a54-8321-b7d2f530b581"] +}, Open ]] +}, +WindowSize->{808, 905}, +WindowMargins->{{344, Automatic}, {Automatic, 0}}, +FrontEndVersion->"12.0 for Mac OS X x86 (64-bit) (April 8, 2019)", +StyleDefinitions->"Default.nb" +] +(* End of Notebook Content *) + +(* Internal cache information *) +(*CellTagsOutline +CellTagsIndex->{} +*) +(*CellTagsIndex +CellTagsIndex->{} +*) +(*NotebookFileOutline +Notebook[{ +Cell[558, 20, 644, 16, 33, "Input",ExpressionUUID->"cdadfb99-0250-46ad-99b1-a2ba73a9dbca"], +Cell[CellGroupData[{ +Cell[1227, 40, 1251, 31, 70, "Input",ExpressionUUID->"07096ba1-9598-42af-8320-3e80c8c747e8"], +Cell[2481, 73, 1019, 26, 72, "Output",ExpressionUUID->"f7dcd4a7-e7c1-4b2f-92cd-26b35afd3cc2"], +Cell[3503, 101, 27080, 464, 234, "Output",ExpressionUUID->"72934ea7-1ec7-47fb-bd5c-ac8822645804"] +}, Open ]], +Cell[CellGroupData[{ +Cell[30620, 570, 670, 16, 30, "Input",ExpressionUUID->"ad755b77-3f52-4d4e-9ccb-49dccd64a83d"], +Cell[31293, 588, 15996, 282, 239, "Output",ExpressionUUID->"c1b74a19-90be-43e3-93be-8eb3dd0e5619"] +}, Open ]], +Cell[47304, 873, 152, 3, 30, "Input",ExpressionUUID->"e67d289e-56b6-43c5-b72b-9dbd4db072d9"], +Cell[CellGroupData[{ +Cell[47481, 880, 1189, 36, 69, "Input",ExpressionUUID->"2feb4617-4791-4722-bd63-ffbe8607ff19"], +Cell[48673, 918, 180, 2, 34, "Output",ExpressionUUID->"8c45a434-466f-4a54-8321-b7d2f530b581"] +}, Open ]] +} +] +*) + +(* End of internal cache information *) + diff --git a/src/app_main.cpp b/src/app_main.cpp index b4a662d..d1ab0d0 100644 --- a/src/app_main.cpp +++ b/src/app_main.cpp @@ -42,7 +42,6 @@ std::unique_ptr make_subprog_boilerlate(); std::unique_ptr make_subprog_partdom(); std::unique_ptr make_subprog_sedov(); std::unique_ptr make_subprog_cloud(); -std::unique_ptr make_subprog_binary_v1(); std::unique_ptr make_subprog_binary(); std::unique_ptr make_subprog_amrsand(); std::unique_ptr make_subprog_test(); @@ -59,7 +58,6 @@ int main(int argc, const char* argv[]) if constexpr (MARA_COMPILE_SUBPROGRAM_PARTDOM) programs["partdom"] = make_subprog_partdom(); if constexpr (MARA_COMPILE_SUBPROGRAM_SEDOV) programs["sedov"] = make_subprog_sedov(); if constexpr (MARA_COMPILE_SUBPROGRAM_CLOUD) programs["cloud"] = make_subprog_cloud(); - if constexpr (MARA_COMPILE_SUBPROGRAM_BINARY_V1) programs["binary_v1"] = make_subprog_binary_v1(); if constexpr (MARA_COMPILE_SUBPROGRAM_BINARY) programs["binary"] = make_subprog_binary(); if constexpr (MARA_COMPILE_SUBPROGRAM_AMRSAND) programs["amrsand"] = make_subprog_amrsand(); if constexpr (MARA_COMPILE_SUBPROGRAM_TEST) programs["test"] = make_subprog_test(); diff --git a/src/core_hdf5.hpp b/src/core_hdf5.hpp index de2e09b..dcaf458 100644 --- a/src/core_hdf5.hpp +++ b/src/core_hdf5.hpp @@ -823,6 +823,10 @@ class h5::Location GroupType require_group(const std::string& name) { + if (name == "/") + { + return open_group(name); + } if (link.contains(name, Object::group)) { return open_group(name); diff --git a/src/core_sequence.hpp b/src/core_sequence.hpp index 26f7b8b..a823792 100644 --- a/src/core_sequence.hpp +++ b/src/core_sequence.hpp @@ -90,7 +90,7 @@ namespace mara * identity of the value type. */ template -struct mara::arithmetic_sequence_t +struct mara::arithmetic_sequence_t final { @@ -435,22 +435,19 @@ struct mara::arithmetic_sequence_t template auto unary_op_impl(Function&& fn, std::index_sequence) const { - using result_type = arithmetic_sequence_t, Rank>; - return result_type{fn(mara::get(*this))...}; + return make_sequence(fn(mara::get(*this))...); } template auto binary_op_impl(Function&& fn, const T& a, std::index_sequence) const { - using result_type = arithmetic_sequence_t, Rank>; - return result_type{fn(mara::get(*this), a)...}; + return make_sequence(fn(mara::get(*this), a)...); } template auto binary_op_impl(Function&& fn, const arithmetic_sequence_t& v, std::index_sequence) const { - using result_type = arithmetic_sequence_t, Rank>; - return result_type{fn(mara::get(*this), mara::get(v))...}; + return make_sequence(fn(mara::get(*this), mara::get(v))...); } template diff --git a/src/core_test.cpp b/src/core_test.cpp index 221db5d..dc1054e 100644 --- a/src/core_test.cpp +++ b/src/core_test.cpp @@ -33,6 +33,7 @@ #include "core_geometric.hpp" #include "core_matrix.hpp" #include "core_sequence.hpp" +#include "core_tuple.hpp" #include "core_linked_list.hpp" #include "core_tree.hpp" #include "core_ndarray.hpp" @@ -84,6 +85,24 @@ TEST_CASE("sequence algorithms work correctly", "[covariant_sequence]") } } +TEST_CASE("tuples work correctly") +{ + auto a = mara::make_arithmetic_tuple(1, 2.0, 2.1f); + REQUIRE(mara::get<0>(a.set<0>(2)) == 2); + REQUIRE(mara::get<0>(a.map([] (auto x) { return x + x; })) == 2); + REQUIRE(mara::get<1>(a.map([] (auto x) { return x + x; })) == 4.0); + REQUIRE(mara::get<2>(a.map([] (auto x) { return x + x; })) == 4.2f); + REQUIRE(mara::get<0>(a + 1) == 2); + REQUIRE(mara::get<1>(a + 2.0) == 4.0); + REQUIRE(mara::get<2>(a + 2.1f) == 4.2f); + REQUIRE(mara::get<0>(a + a) == 2); + REQUIRE(mara::get<1>(a + a) == 4.0); + REQUIRE(mara::get<2>(a + a) == 4.2f); + REQUIRE(mara::get<1>(a + a * 2.0) == 6.0); + REQUIRE((a < 3).to_sequence().all()); + static_assert(std::is_same>::value); +} + TEST_CASE("linked lists work as expected", "[linked_list]") { SECTION("prepending to build lists works OK") diff --git a/src/core_tuple.hpp b/src/core_tuple.hpp new file mode 100644 index 0000000..f8d82bc --- /dev/null +++ b/src/core_tuple.hpp @@ -0,0 +1,280 @@ +/** + ============================================================================== + Copyright 2019, Jonathan Zrake + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + ============================================================================== +*/ + + + + +#pragma once +#include // std::plus, std::multiplies, etc... +#include // std::tuple +#include "core_sequence.hpp" + + + + +//============================================================================= +namespace mara +{ + template struct arithmetic_tuple_t; + template struct derivable_tuple_t; + + template + auto make_arithmetic_tuple(Types... args); + + template + const auto& get(const arithmetic_tuple_t& tup); +} + + + + +/** + * @brief Class for working with tuples of arithmetic types. Arithmetic + * operations apply to the values element-wise. + * + * @tparam Types The tuple types + * + * @note Arithmetic operations are supported between tuples of different + * types and identical sizes, as long as the operation is defined on + * the corresponding elements. + */ +template +struct mara::arithmetic_tuple_t final +{ + + + + + /** + * @brief Immutable set method; return a new tuple with the value at + * the given index replaced. + * + * @param[in] value The value to put in instead + * + * @tparam Index The index to replace + * @tparam ValueType The value type (cast to the corresponding type in + * this tuple). + * + * @return A new tuple + */ + template + auto set(const ValueType& value) const + { + auto result = *this; + std::get(result.__impl) = value; + return result; + } + + + + + /** + * @brief Map a function over the elements of this tuple, returning + * another whose value type is the function's return applied to + * each element. + * + * @param fn The function to map + * + * @tparam Function The type of the function object + * + * @return A new tuple + * + * @note This method makes a tuple into a "functor" (a formal functor, + * not the common misnomer in C++ really meaning function + * object). + */ + template + auto map(Function&& fn) const + { + return unary_op(std::forward(fn)); + } + + auto to_sequence() const + { + return to_sequence_impl>(std::make_index_sequence()); + } + + + //========================================================================= + template auto operator+ (const T&a) const { return binary_op(a, std::plus<>()); } + template auto operator- (const T&a) const { return binary_op(a, std::minus<>()); } + template auto operator* (const T&a) const { return binary_op(a, std::multiplies<>()); } + template auto operator/ (const T&a) const { return binary_op(a, std::divides<>()); } + template auto operator&&(const T&a) const { return binary_op(a, std::logical_and<>()); } + template auto operator||(const T&a) const { return binary_op(a, std::logical_or<>()); } + template auto operator==(const T&a) const { return binary_op(a, std::equal_to<>()); } + template auto operator!=(const T&a) const { return binary_op(a, std::not_equal_to<>()); } + template auto operator<=(const T&a) const { return binary_op(a, std::less_equal<>()); } + template auto operator>=(const T&a) const { return binary_op(a, std::greater_equal<>()); } + template auto operator< (const T&a) const { return binary_op(a, std::less<>()); } + template auto operator> (const T&a) const { return binary_op(a, std::greater<>()); } + + template auto operator+ (const arithmetic_tuple_t& v) const { return binary_op(v, std::plus<>()); } + template auto operator- (const arithmetic_tuple_t& v) const { return binary_op(v, std::minus<>()); } + template auto operator* (const arithmetic_tuple_t& v) const { return binary_op(v, std::multiplies<>()); } + template auto operator/ (const arithmetic_tuple_t& v) const { return binary_op(v, std::divides<>()); } + template auto operator&&(const arithmetic_tuple_t& v) const { return binary_op(v, std::logical_and<>()); } + template auto operator||(const arithmetic_tuple_t& v) const { return binary_op(v, std::logical_or<>()); } + template auto operator==(const arithmetic_tuple_t& v) const { return binary_op(v, std::equal_to<>()); } + template auto operator!=(const arithmetic_tuple_t& v) const { return binary_op(v, std::not_equal_to<>()); } + template auto operator<=(const arithmetic_tuple_t& v) const { return binary_op(v, std::less_equal<>()); } + template auto operator>=(const arithmetic_tuple_t& v) const { return binary_op(v, std::greater_equal<>()); } + template auto operator< (const arithmetic_tuple_t& v) const { return binary_op(v, std::less<>()); } + template auto operator> (const arithmetic_tuple_t& v) const { return binary_op(v, std::greater<>()); } + + auto operator+() const { return unary_op([] (auto&& x) { return +x; }); } + auto operator-() const { return unary_op([] (auto&& x) { return -x; }); } + + + + + //========================================================================= + template + auto unary_op_impl(Function&& fn, std::index_sequence) const + { + return make_arithmetic_tuple(fn(mara::get(*this))...); + } + + template + auto binary_op_impl(Function&& fn, const T& a, std::index_sequence) const + { + return make_arithmetic_tuple(fn(mara::get(*this), a)...); + } + + template + auto binary_op_impl(Function&& fn, const arithmetic_tuple_t& v, std::index_sequence) const + { + return make_arithmetic_tuple(fn(mara::get(*this), mara::get(v))...); + } + + template + auto to_sequence_impl(std::index_sequence) const + { + return make_sequence(TargetType(mara::get(*this))...); + } + + template + auto unary_op(Function&& fn) const + { + return unary_op_impl(fn, std::make_index_sequence()); + } + + template + auto binary_op(const T& a, Function&& fn) const + { + return binary_op_impl(fn, a, std::make_index_sequence()); + } + + template + auto binary_op(const arithmetic_tuple_t& v, Function&& fn) const + { + static_assert(sizeof...(Types) == sizeof...(OtherTypes), "binary operation between tuples of different size"); + return binary_op_impl(fn, v, std::make_index_sequence()); + } + + + + + //========================================================================= + std::tuple __impl; +}; + + + + +/** + * @brief Class for working with tuples of native types - floats, doubles, + * etc. + * + * @tparam ValueType The underlying value type + * @tparam Rank The dimensionality + * @tparam DerivedType The CRTP class (google 'curiously recurring template + * pattern') + * + * @note You can add/subtract other sequences of the same rank and type, + * and you can multiply the whole sequence by scalars of the same + * value type. Arithmetic operations all return another instance of + * the same class type. For sequences that are covariant in the + * value type, see the arithmetic_tuple_t. This class is not used + * directly; you should inherit it with the CRTP pattern. This means + * that type identity is defined by the name of the derived class + * (different derived classes with the same rank and value type are + * not considered equal by the compiler). + */ +template +struct mara::derivable_tuple_t +{ + //========================================================================= + bool operator==(const DerivedType& other) const { return __impl.operator==(other.__impl).to_sequenc().all(); } + bool operator!=(const DerivedType& other) const { return __impl.operator!=(other.__impl).to_sequenc().any(); } + template DerivedType operator*(const T& other) const { return DerivedType{{__impl.operator*(other)}}; } + template DerivedType operator/(const T& other) const { return DerivedType{{__impl.operator/(other)}}; } + DerivedType operator+(const DerivedType& other) const { return DerivedType{{__impl.operator+(other.__impl)}}; } + DerivedType operator-(const DerivedType& other) const { return DerivedType{{__impl.operator-(other.__impl)}}; } + DerivedType operator+() const { return DerivedType{{__impl.operator+()}}; } + DerivedType operator-() const { return DerivedType{{__impl.operator-()}}; } + + //========================================================================= + arithmetic_tuple_t __impl; +}; + + + + + +/** + * @brief Make a new sequence with inferred type and size. + * + * @param[in] args The elements + * + * @tparam Args The element types + * @tparam ValueType The inferred type, if a common type can be inferred + * + * @return The sequence + * @note You can override the inferred value type by doing e.g. + * make_sequence(1, 2). + */ +template +auto mara::make_arithmetic_tuple(Types... args) +{ + return arithmetic_tuple_t{std::tuple(args...)}; +} + + + + +/** + * @brief Type-safe indexing (preferred over operator[] where possible + * for safety). + * + * @tparam Index The index to get + * + * @return The value at the given index + */ +template +const auto& mara::get(const arithmetic_tuple_t& tup) +{ + return std::get(tup.__impl); +} diff --git a/src/mesh_tree_operators.hpp b/src/mesh_tree_operators.hpp index 415f0bd..7a3f93f 100644 --- a/src/mesh_tree_operators.hpp +++ b/src/mesh_tree_operators.hpp @@ -55,6 +55,9 @@ namespace mara std::size_t zones_per_block, std::size_t depth); + template + auto get_cell_block(const arithmetic_binary_tree_t& tree, tree_index_t index, PostFunction&& post); + template auto get_cell_block(const arithmetic_binary_tree_t& tree, tree_index_t index); @@ -185,23 +188,28 @@ mara::amr_types::vertex_2d_tree_t mara::create_vertex_quadtree( * cell-like density (not mass) data. Throws an exception if the * tree has over-refined neighbors. * - * @param[in] tree The tree of blocks - * @param[in] index The target index + * @param[in] tree The tree of blocks + * @param[in] index The target index + * @param post An operator that is applied to the (unevaluated) + * block. This would commonly be a selection (since + * the whole block may not be required) followed by + * nd::to_shared. * - * @tparam ValueType The value type of the array in the tree (must be a - * shared array of some type) - * @tparam Rank The rank of the tree + * @tparam ValueType The value type of the array in the tree (must be a + * shared array of some type) + * @tparam Rank The rank of the tree + * @tparam PostFunction The type of the post operator * * @return The retrieved or manufactured block of cells */ -template -auto mara::get_cell_block(const arithmetic_binary_tree_t& tree, tree_index_t index) +template +auto mara::get_cell_block(const arithmetic_binary_tree_t& tree, tree_index_t index, PostFunction&& post) { try { // If the tree has a value at the target index, then return that value. if (tree.contains(index)) { - return tree.at(index); + return tree.at(index) | post; } // If the tree has a value at the node above the target index, then refine @@ -210,14 +218,14 @@ auto mara::get_cell_block(const arithmetic_binary_tree_t& tree, if (tree.contains(index.parent_index())) { auto ib = mara::to_integral(index.relative_to_parent().orthant()); - return (tree.at(index.parent_index()) | mara::refine_cells())[ib].shared(); + return (tree.at(index.parent_index()) | mara::refine_cells())[ib] | post; } // If the target index is not a leaf, then combine the data from its // children, and then coarsen it. - return mara::combine_cells(index.child_indexes().map([tree] (auto i) { return get_cell_block(tree, i); })) + return mara::combine_cells(index.child_indexes().map([tree] (auto i) { return get_cell_block(tree, i, nd::to_shared()); })) | mara::coarsen_cells() - | nd::to_shared(); + | post; } catch (const std::exception& e) { @@ -225,6 +233,12 @@ auto mara::get_cell_block(const arithmetic_binary_tree_t& tree, } } +template +auto mara::get_cell_block(const arithmetic_binary_tree_t& tree, tree_index_t index) +{ + return get_cell_block(tree, index, nd::to_shared()); +} + diff --git a/src/physics_iso2d.hpp b/src/physics_iso2d.hpp index 34add59..f75172d 100644 --- a/src/physics_iso2d.hpp +++ b/src/physics_iso2d.hpp @@ -30,6 +30,8 @@ #include #include "core_geometric.hpp" #include "core_matrix.hpp" +#include "core_sequence.hpp" +#include "core_tuple.hpp" @@ -48,10 +50,53 @@ struct mara::iso2d using unit_flow = dimensional_value_t< 0, 1,-1, double>; using unit_flux = dimensional_value_t<-1, 1,-1, double>; - using conserved_per_area_t = arithmetic_sequence_t; - using conserved_t = arithmetic_sequence_t; - using flow_t = arithmetic_sequence_t; - using flux_t = arithmetic_sequence_t; + // using conserved_per_area_t = arithmetic_sequence_t; + // using conserved_t = arithmetic_sequence_t; + // using flow_t = arithmetic_sequence_t; + // using flux_t = arithmetic_sequence_t; + using location_2d_t = arithmetic_sequence_t, 2>; + + + + + using conserved_t = arithmetic_tuple_t< + dimensional_value_t<0, 1, 0, double>, + dimensional_value_t<1, 1,-1, double>, + dimensional_value_t<1, 1,-1, double> + >; + + using conserved_per_area_t = arithmetic_tuple_t< + dimensional_value_t<-2, 1, 0, double>, + dimensional_value_t<-1, 1,-1, double>, + dimensional_value_t<-1, 1,-1, double> + >; + + using flux_t = arithmetic_tuple_t< + dimensional_value_t<-1, 1,-1, double>, + dimensional_value_t< 0, 1,-2, double>, + dimensional_value_t< 0, 1,-2, double> + >; + + + + + using conserved_angmom_t = arithmetic_tuple_t< + dimensional_value_t<0, 1, 0, double>, + dimensional_value_t<2, 1,-1, double>, + dimensional_value_t<2, 1,-1, double> + >; + + using conserved_angmom_per_area_t = arithmetic_tuple_t< + dimensional_value_t<-2, 1, 0, double>, + dimensional_value_t< 0, 1,-1, double>, + dimensional_value_t< 0, 1,-1, double> + >; + + using flux_angmom_t = arithmetic_tuple_t< + dimensional_value_t<-1, 1,-1, double>, + dimensional_value_t< 1, 1,-2, double>, + dimensional_value_t< 1, 1,-2, double> + >; struct primitive_t; struct wavespeeds_t @@ -62,8 +107,15 @@ struct mara::iso2d struct riemann_hllc_variables_t; static inline primitive_t recover_primitive( - const conserved_per_area_t& U, - double density_floor); + const conserved_per_area_t& U); + + static inline primitive_t recover_primitive( + const conserved_angmom_per_area_t& Q, + const location_2d_t& x); + + static inline flux_angmom_t to_conserved_angmom_flux( + const flux_t &F, + const location_2d_t& x); static inline primitive_t roe_average( const primitive_t& Pl, @@ -118,7 +170,7 @@ struct mara::iso2d::primitive_t : public mara::derivable_sequence_t(sigma()); + return {{ + S, + S * v[0], + S * v[1], + }}; + } + + + + + conserved_angmom_per_area_t to_conserved_angmom_per_area(location_2d_t x) const + { + auto v = make_sequence(make_velocity(velocity_x()), make_velocity(velocity_y())); + auto S = make_dimensional<-2, 1, 0>(sigma()); + return {{ + S, + S * (x[0] * v[0] + x[1] * v[1]), + S * (x[0] * v[1] - x[1] * v[0]) + }}; } @@ -213,11 +281,15 @@ struct mara::iso2d::primitive_t : public mara::derivable_sequence_t(v * sigma()) + .set<1>(v * sigma() * velocity_x() + p * nhat.get_n1()) + .set<2>(v * sigma() * velocity_y() + p * nhat.get_n2()); + + // F[0] = v * sigma(); + // F[1] = v * sigma() * velocity_x() + p * nhat.get_n1(); + // F[2] = v * sigma() * velocity_y() + p * nhat.get_n2(); + // return F; } @@ -253,29 +325,68 @@ struct mara::iso2d::primitive_t : public mara::derivable_sequence_t(U); + auto vx = mara::get<1>(U) / sigma; + auto vy = mara::get<2>(U) / sigma; + + if (sigma < 0.0) { - if (density_floor == 0.0) - { - throw std::invalid_argument("mara::iso2d::recover_primitive (negative density)"); - } - else - { - // This is a pretty extreme measure. If it happens too often you - // should expect other things to go wrong. - return primitive_t() - .with_sigma(density_floor) - .with_velocity_x(0.0) - .with_velocity_y(0.0); - } + throw std::invalid_argument("mara::iso2d::recover_primitive (negative density)"); } - auto P = primitive_t(); - P[0] = U[0].value; - P[1] = U[1].value / P[0]; - P[2] = U[2].value / P[0]; - return P; + return {{sigma.value, vx.value, vy.value}}; +} + + + + +/** + * @brief Attempt to recover a primitive variable state from the given + * vector of angular momentum conserving (area) densities. + * + * @param[in] Q The angular momentum conserving variables (sigma, Sr, Lz) + * @param[in] x The 2d position + * + * @return A primitive variable state, if the recovery succeeds + */ +mara::iso2d::primitive_t mara::iso2d::recover_primitive(const conserved_angmom_per_area_t& Q, const location_2d_t& x) +{ + auto sigma = mara::get<0>(Q); + auto sr = mara::get<1>(Q) / sigma; + auto lz = mara::get<2>(Q) / sigma; + auto r2 = (x * x).sum(); + auto vx = (sr * x[0] - lz * x[1]) / r2; + auto vy = (sr * x[1] + lz * x[0]) / r2; + + if (sigma < 0.0) + { + throw std::invalid_argument("mara::iso2d::recover_primitive (negative density)"); + } + return {{sigma.value, vx.value, vy.value}}; +} + + + + +/** + * @brief Convert a flux of conserved quantities to a flux of + * angumar-momentum conserving quantities: + * + * F(Sr) = x F(px) + y F(py) + * F(Lz) = x F(py) - y F(px) + * + * @param[in] F The fluxes of linear momentum conserving quantities + * @param[in] x The (two-dimensional) position + * + * @return A fluxes of angular-momentum conserving quantities + */ +mara::iso2d::flux_angmom_t mara::iso2d::to_conserved_angmom_flux(const flux_t& F, const location_2d_t& x) +{ + auto sigma_flux = mara::get<0>(F); + auto sr_flux = x[0] * mara::get<1>(F) + x[1] * mara::get<2>(F); + auto lz_flux = x[0] * mara::get<2>(F) - x[1] * mara::get<1>(F); + return {{sigma_flux, sr_flux, lz_flux}}; } @@ -310,10 +421,10 @@ mara::iso2d::primitive_t mara::iso2d::roe_average( * * @param[in] Pl The state to the left of the interface * @param[in] Pr The state to the right - * @param[in] sound_speed_squared_l The sound speed squared to the left of the interface + * @param[in] sound_speed_squared_l The sound speed squared to the left of the + * interface * @param[in] sound_speed_squared_r The sound speed squared to the right * @param[in] nhat The normal vector to the interface - * @param[in] gamma_law_index The gamma law index * * @return A vector of fluxes */ diff --git a/src/physics_test.cpp b/src/physics_test.cpp index 27c4e8c..e6e7c11 100644 --- a/src/physics_test.cpp +++ b/src/physics_test.cpp @@ -105,9 +105,9 @@ TEST_CASE("Isothermal 2d system", "[mara::iso2d::primitive_t]") auto U = P.to_conserved_per_area(); REQUIRE(P.velocity_x() == 0.5); - REQUIRE(U[0].value == P.sigma()); - REQUIRE(U[1].value == P.sigma() * P.velocity_x()); - REQUIRE(U[2].value == P.sigma() * P.velocity_y()); + REQUIRE(mara::get<0>(U).value == P.sigma()); + REQUIRE(mara::get<1>(U).value == P.sigma() * P.velocity_x()); + REQUIRE(mara::get<2>(U).value == P.sigma() * P.velocity_y()); SECTION("HLLC gets zero contact speed for zero-velocity, equal pressure states") { diff --git a/src/subprog_binary.cpp b/src/subprog_binary.cpp index 471775b..ff91b2f 100644 --- a/src/subprog_binary.cpp +++ b/src/subprog_binary.cpp @@ -45,7 +45,7 @@ namespace binary auto next_solution(const solution_t& solution, const solver_data_t& solver_data); auto next_schedule(const state_t& state); auto next_state(const state_t& state, const solver_data_t& solver_data); - auto run_tasks(const state_t& state); + auto run_tasks(const state_t& state, const solver_data_t& solver_data); auto simulation_should_continue(const state_t& state); } @@ -60,11 +60,11 @@ mara::config_template_t binary::create_config_template() .item("outdir", "data") // directory where data products are written to .item("cpi", 10.0) // checkpoint interval (orbits; chkpt.????.h5 - snapshot of app_state) .item("dfi", 1.0) // diagnostic field interval (orbits; diagnostics.????.h5) - .item("tsi", 0.1) // time series interval (orbits) + .item("tsi", 2e-3) // time series interval (orbits) .item("tfinal", 1.0) // simulation stop time (orbits) .item("cfl_number", 0.4) // the Courant number to use .item("depth", 4) - .item("block_size", 32) + .item("block_size", 24) .item("focus_factor", 2.00) .item("focus_index", 2.00) .item("threaded", 1) // set to 0 to disable multi-threaded tree updates @@ -72,16 +72,18 @@ mara::config_template_t binary::create_config_template() .item("reconstruct_method", "plm") // zone extrapolation method: pcm or plm .item("plm_theta", 1.8) // plm theta parameter: [1.0, 2.0] .item("riemann", "hlle") // riemann solver to use: hlle only (hllc disabled until further testing) - .item("softening_radius", 0.05) // gravitational softening radius - .item("sink_radius", 0.05) // radius of mass (and momentum) subtraction region + .item("softening_radius", 0.02) // gravitational softening radius + .item("sink_radius", 0.02) // radius of mass (and momentum) subtraction region .item("sink_rate", 1e2) // sink rate at the point masses (orbital angular frequency) - .item("domain_radius", 12.0) // half-size of square domain + .item("buffer_damping_rate", 1.0) // maximum rate of buffer zone, where solution is driven to initial state + .item("domain_radius", 24.0) // half-size of square domain + .item("disk_radius", 2.0) // characteristic disk radius (in units of binary separation) + .item("ambient_density", 1e-4) // surface density beyond torus .item("separation", 1.0) // binary separation: 0.0 or 1.0 (zero emulates a single body) .item("mass_ratio", 1.0) // binary mass ratio M2 / M1: (0.0, 1.0] .item("eccentricity", 0.0) // orbital eccentricity: [0.0, 1.0) - .item("buffer_damping_rate", 10.0) // maximum rate of buffer zone, where solution is driven to initial state .item("counter_rotate", 0) // retrograde disk option: 0 or 1 - .item("mach_number", 10.0); + .item("mach_number", 40.0); } @@ -91,26 +93,43 @@ mara::config_template_t binary::create_config_template() binary::primitive_field_t binary::create_disk_profile(const mara::config_t& run_config) { auto softening_radius = run_config.get_double("softening_radius"); + auto disk_radius = run_config.get_double("disk_radius"); + auto mach_number = run_config.get_double("mach_number"); + auto ambient_density = run_config.get_double("ambient_density"); auto counter_rotate = run_config.get_int("counter_rotate"); + auto rc = disk_radius; + auto s1 = ambient_density; + + auto sigma = [=] (double r) + { + return std::exp(-0.5 * (r / rc - 1) * (r / rc - 1)) + s1; + }; + + auto dlogsigma_dlogr = [=] (double r) + { + auto x = r / rc; + return x * (1 - x) * (1 - s1 / sigma(r)); + }; return [=] (location_2d_t point) { auto GM = 1.0; + auto cs2 = 1.0 / mach_number / mach_number; // constant sound-speed auto x = point[0].value; auto y = point[1].value; auto rs = softening_radius; - auto rc = 2.5; auto r2 = x * x + y * y; auto r = std::sqrt(r2); - auto sigma = std::exp(-std::min(5.0, std::pow(r - rc, 2) / rc / 2)); - auto ag = -GM * std::pow(r2 + rs * rs, -1.5) * r; - auto omega2 = -ag / r; - auto vp = (counter_rotate ? -1 : 1) * r * std::sqrt(omega2); + auto gradp_term = cs2 * dlogsigma_dlogr(r); + auto vp = std::sqrt(GM / (r + rs) + gradp_term) * (counter_rotate ? -1 : 1); auto vx = vp * (-y / r); auto vy = vp * ( x / r); + if (GM / (r + rs) + gradp_term < 0.0) + throw std::invalid_argument("no disk solution: increase mach_number or ambient_density"); + return mara::iso2d::primitive_t() - .with_sigma(sigma) + .with_sigma(sigma(r)) .with_velocity_x(vx) .with_velocity_y(vy); }; @@ -168,7 +187,7 @@ binary::solution_t binary::create_solution(const mara::config_t& run_config) | nd::map(std::mem_fn(&mara::iso2d::primitive_t::to_conserved_per_area)) | nd::to_shared(); }); - return solution_t{0, 0.0, conserved, {}, {}, {}}; + return solution_t{0, 0.0, conserved, {}, {}, {}, {}, {}, {}}; } mara::schedule_t binary::create_schedule(const mara::config_t& run_config) @@ -246,14 +265,14 @@ auto binary::next_state(const state_t& state, const solver_data_t& solver_data) //============================================================================= auto binary::simulation_should_continue(const state_t& state) { - return state.solution.time / (2 * M_PI) < state.run_config.get("tfinal"); + return state.solution.time / (2 * M_PI) < state.run_config.get_double("tfinal"); } //============================================================================= -auto binary::run_tasks(const state_t& state) +auto binary::run_tasks(const state_t& state, const solver_data_t& solver_data) { @@ -285,23 +304,28 @@ auto binary::run_tasks(const state_t& state) //========================================================================= - auto record_time_series = [] (state_t state) + auto record_time_series = [&solver_data] (state_t state) { auto sample = time_series_sample_t(); - sample.time = state.solution.time; - sample.total_disk_mass = 0.0; // TODO - sample.mass_accreted_on = state.solution.mass_accreted_on; - sample.integrated_torque_on = state.solution.integrated_torque_on; - sample.work_done_on = state.solution.work_done_on; + sample.time = state.solution.time; + sample.mass_accreted_on = state.solution.mass_accreted_on; + sample.angular_momentum_accreted_on = state.solution.angular_momentum_accreted_on; + sample.integrated_torque_on = state.solution.integrated_torque_on; + sample.work_done_on = state.solution.work_done_on; + sample.mass_ejected = state.solution.mass_ejected; + sample.angular_momentum_ejected = state.solution.angular_momentum_ejected; + sample.disk_mass = disk_mass (state.solution, solver_data); + sample.disk_angular_momentum = disk_angular_momentum(state.solution, solver_data); + state.time_series = state.time_series.prepend(sample); return mara::complete_task_in(state, "record_time_series"); }; return mara::run_scheduled_tasks(state, { - {"write_checkpoint", write_checkpoint}, {"write_diagnostics", write_diagnostics}, - {"record_time_series", record_time_series}}); + {"record_time_series", record_time_series}, + {"write_checkpoint", write_checkpoint}}); } void binary::prepare_filesystem(const mara::config_t& run_config) @@ -337,20 +361,21 @@ class subprog_binary : public mara::sub_program_t auto solver_data = binary::create_solver_data(run_config); auto state = binary::create_state(run_config); auto next = std::bind(binary::next_state, std::placeholders::_1, solver_data); + auto tasks = std::bind(binary::run_tasks, std::placeholders::_1, solver_data); auto perf = mara::perf_diagnostics_t(); binary::prepare_filesystem(run_config); binary::set_scheme_globals(run_config); mara::pretty_print(std::cout, "config", run_config); - state = binary::run_tasks(state); + state = tasks(state); while (binary::simulation_should_continue(state)) { - std::tie(state, perf) = mara::time_execution(mara::compose(binary::run_tasks, next), state); + std::tie(state, perf) = mara::time_execution(mara::compose(tasks, next), state); binary::print_run_loop_message(state, perf); } - run_tasks(next(state)); + tasks(next(state)); return 0; } diff --git a/src/subprog_binary.hpp b/src/subprog_binary.hpp index 910861c..154ff27 100644 --- a/src/subprog_binary.hpp +++ b/src/subprog_binary.hpp @@ -87,6 +87,8 @@ namespace binary riemann_solver_t riemann_solver; mara::two_body_parameters_t binary_params; quad_tree_t vertices; + quad_tree_t cell_centers; + quad_tree_t> cell_areas; quad_tree_t initial_conserved; quad_tree_t> buffer_rate_field; }; @@ -100,8 +102,11 @@ namespace binary quad_tree_t conserved; mara::arithmetic_sequence_t, 2> mass_accreted_on = {}; + mara::arithmetic_sequence_t, 2> angular_momentum_accreted_on = {}; mara::arithmetic_sequence_t, 2> integrated_torque_on = {}; mara::arithmetic_sequence_t, 2> work_done_on = {}; + mara::unit_mass mass_ejected = {}; + mara::unit_angmom angular_momentum_ejected = {}; solution_t operator+(const solution_t& other) const; solution_t operator*(mara::rational_number_t scale) const; @@ -125,9 +130,14 @@ namespace binary //========================================================================= struct time_series_sample_t { - mara::unit_time time = 0.0; - mara::unit_mass total_disk_mass = 0.0; + mara::unit_time time = 0.0; + mara::unit_mass disk_mass = 0.0; + mara::unit_angmom disk_angular_momentum = 0.0; + mara::unit_mass mass_ejected = {}; + mara::unit_angmom angular_momentum_ejected = {}; + mara::arithmetic_sequence_t, 2> mass_accreted_on = {}; + mara::arithmetic_sequence_t, 2> angular_momentum_accreted_on = {}; mara::arithmetic_sequence_t, 2> integrated_torque_on = {}; mara::arithmetic_sequence_t, 2> work_done_on = {}; }; @@ -159,8 +169,10 @@ namespace binary solver_data_t create_solver_data (const mara::config_t& run_config); primitive_field_t create_disk_profile (const mara::config_t& run_config); - solution_t advance(const solution_t& solution, const solver_data_t& solver_data, mara::unit_time dt); - diagnostic_fields_t diagnostic_fields(const solution_t& solution, const mara::config_t& run_config); + diagnostic_fields_t diagnostic_fields (const solution_t& solution, const mara::config_t& run_config); + solution_t advance (const solution_t& solution, const solver_data_t& solver_data, mara::unit_time dt, bool safe_mode=false); + mara::unit_mass disk_mass (const solution_t& solution, const solver_data_t& solver_data); + mara::unit_angmom disk_angular_momentum(const solution_t& solution, const solver_data_t& solver_data); void set_scheme_globals (const mara::config_t& run_config); void prepare_filesystem (const mara::config_t& run_config); diff --git a/src/subprog_binary_diagnostics.cpp b/src/subprog_binary_diagnostics.cpp index 5bebe04..0fb9b45 100644 --- a/src/subprog_binary_diagnostics.cpp +++ b/src/subprog_binary_diagnostics.cpp @@ -6,31 +6,56 @@ //============================================================================= -binary::diagnostic_fields_t binary::diagnostic_fields(const solution_t& solution, const mara::config_t& run_config) +template +static auto component() { - auto solver_data = create_solver_data(run_config); - auto binary = mara::compute_two_body_state(solver_data.binary_params, solution.time.value); + return nd::map([] (auto p) { return mara::get(p); }); +}; + - auto component = [] (std::size_t component) - { - return nd::map([component] (auto p) { return p[component]; }); - }; - auto area_from_vertices = [component] (auto vertices) - { - auto dx = vertices | component(0) | nd::difference_on_axis(0) | nd::midpoint_on_axis(1); - auto dy = vertices | component(1) | nd::difference_on_axis(1) | nd::midpoint_on_axis(0); - return dx * dy; - }; - auto recover_primitive = std::bind(mara::iso2d::recover_primitive, std::placeholders::_1, 0.0); +//============================================================================= +mara::unit_mass binary::disk_mass(const solution_t& solution, const solver_data_t& solver_data) +{ + auto v0 = solver_data.vertices; + auto dA = solver_data.cell_areas; + auto u0 = solution.conserved; + auto sigma = u0.map(component<0>()); + return (sigma * dA).map(nd::sum()).sum(); +} + +mara::unit_angmom binary::disk_angular_momentum(const solution_t& solution, const solver_data_t& solver_data) +{ auto v0 = solver_data.vertices; + auto dA = solver_data.cell_areas; + auto u0 = solution.conserved; auto c0 = v0.map(nd::midpoint_on_axis(0)).map(nd::midpoint_on_axis(1)); - auto xc = c0.map(component(0)); - auto yc = c0.map(component(1)); + auto xc = c0.map(component<0>()); + auto yc = c0.map(component<1>()); + auto px = u0.map(component<1>()); + auto py = u0.map(component<2>()); + auto Lz = xc * py - yc * px; + return (Lz * dA).map(nd::sum()).sum(); +} + + + + +//============================================================================= +binary::diagnostic_fields_t binary::diagnostic_fields(const solution_t& solution, const mara::config_t& run_config) +{ + auto solver_data = create_solver_data(run_config); + auto binary = mara::compute_two_body_state(solver_data.binary_params, solution.time.value); + + auto recover_primitive = [] (auto&& u) { return mara::iso2d::recover_primitive(u); }; + auto v0 = solver_data.vertices; + auto c0 = solver_data.cell_centers; + auto xc = c0.map(component<0>()); + auto yc = c0.map(component<1>()); auto u0 = solution.conserved; auto p0 = u0.map(nd::map(recover_primitive)).map(nd::to_shared()); - auto dA = v0.map(area_from_vertices).map(nd::to_shared()); + auto dA = solver_data.cell_areas; auto rc = (xc * xc + yc * yc).map(nd::map([] (mara::unit_area r2) { return r2.pow<1, 2>(); })); auto rhat_x = xc / rc; diff --git a/src/subprog_binary_io.cpp b/src/subprog_binary_io.cpp index 1d305f2..2f4cef0 100644 --- a/src/subprog_binary_io.cpp +++ b/src/subprog_binary_io.cpp @@ -5,6 +5,40 @@ +//============================================================================= +template<> +struct h5::hdf5_type_info +{ + using native_type = mara::iso2d::conserved_per_area_t; + static auto make_datatype_for(const native_type& value) { return h5::Datatype::native_double().as_array(3); } + static auto make_dataspace_for(const native_type& value) { return Dataspace::scalar(); } + static auto convert_to_writable(const native_type& value) { return value; } + static auto prepare(const Datatype&, const Dataspace& space) { return native_type(); } + static auto finalize(native_type&& value) { return std::move(value); } + static auto get_address(const native_type& value) { return &value; } + static auto get_address(native_type& value) { return &value; } +}; + + + + +//============================================================================= +template<> +struct h5::hdf5_type_info +{ + using native_type = mara::iso2d::conserved_angmom_per_area_t; + static auto make_datatype_for(const native_type& value) { return h5::Datatype::native_double().as_array(3); } + static auto make_dataspace_for(const native_type& value) { return Dataspace::scalar(); } + static auto convert_to_writable(const native_type& value) { return value; } + static auto prepare(const Datatype&, const Dataspace& space) { return native_type(); } + static auto finalize(native_type&& value) { return std::move(value); } + static auto get_address(const native_type& value) { return &value; } + static auto get_address(native_type& value) { return &value; } +}; + + + + //============================================================================= template<> struct h5::hdf5_type_info @@ -14,10 +48,14 @@ struct h5::hdf5_type_info { return h5::Datatype::compound({ h5_compound_type_member(native_type, time), - h5_compound_type_member(native_type, total_disk_mass), + h5_compound_type_member(native_type, disk_mass), + h5_compound_type_member(native_type, disk_angular_momentum), h5_compound_type_member(native_type, mass_accreted_on), + h5_compound_type_member(native_type, angular_momentum_accreted_on), h5_compound_type_member(native_type, integrated_torque_on), h5_compound_type_member(native_type, work_done_on), + h5_compound_type_member(native_type, mass_ejected), + h5_compound_type_member(native_type, angular_momentum_ejected), }); } static auto make_dataspace_for(const native_type& value) { return Dataspace::scalar(); } @@ -36,12 +74,15 @@ template<> void mara::write(h5::Group& group, std::string name, const binary::solution_t& solution) { auto location = group.require_group(name); - mara::write(location, "time", solution.time); - mara::write(location, "iteration", solution.iteration); - mara::write(location, "conserved", solution.conserved); - mara::write(location, "mass_accreted_on", solution.mass_accreted_on); - mara::write(location, "integrated_torque_on", solution.integrated_torque_on); - mara::write(location, "work_done_on", solution.work_done_on); + mara::write(location, "time", solution.time); + mara::write(location, "iteration", solution.iteration); + mara::write(location, "conserved", solution.conserved); + mara::write(location, "mass_accreted_on", solution.mass_accreted_on); + mara::write(location, "angular_momentum_ejected", solution.angular_momentum_ejected); + mara::write(location, "integrated_torque_on", solution.integrated_torque_on); + mara::write(location, "work_done_on", solution.work_done_on); + mara::write(location, "mass_ejected", solution.mass_ejected); + mara::write(location, "angular_momentum_ejected", solution.angular_momentum_ejected); } template<> @@ -72,12 +113,15 @@ template<> void mara::read(h5::Group& group, std::string name, binary::solution_t& solution) { auto location = group.open_group(name); - mara::read(location, "time", solution.time); - mara::read(location, "iteration", solution.iteration); - mara::read(location, "conserved", solution.conserved); - mara::read(location, "mass_accreted_on", solution.mass_accreted_on); - mara::read(location, "integrated_torque_on", solution.integrated_torque_on); - mara::read(location, "work_done_on", solution.work_done_on); + mara::read(location, "time", solution.time); + mara::read(location, "iteration", solution.iteration); + mara::read(location, "conserved", solution.conserved); + mara::read(location, "mass_accreted_on", solution.mass_accreted_on); + mara::read(location, "angular_momentum_ejected", solution.angular_momentum_ejected); + mara::read(location, "integrated_torque_on", solution.integrated_torque_on); + mara::read(location, "work_done_on", solution.work_done_on); + mara::read(location, "mass_ejected", solution.mass_ejected); + mara::read(location, "angular_momentum_ejected", solution.angular_momentum_ejected); } template<> diff --git a/src/subprog_binary_scheme.cpp b/src/subprog_binary_scheme.cpp index b0127bf..f984951 100644 --- a/src/subprog_binary_scheme.cpp +++ b/src/subprog_binary_scheme.cpp @@ -1,3 +1,4 @@ +#include #include "core_ndarray_ops.hpp" #include "math_interpolation.hpp" #include "mesh_prolong_restrict.hpp" @@ -29,6 +30,12 @@ void binary::set_scheme_globals(const mara::config_t& run_config) //============================================================================= +template +static auto component() +{ + return nd::map([] (auto p) { return mara::get(p); }); +}; + auto binary::grav_vdot_field(const solver_data_t& solver_data, location_2d_t body_location, mara::unit_mass body_mass) { auto accel = [body_location, body_mass, softening_radius=solver_data.softening_radius](location_2d_t field_point) @@ -72,24 +79,8 @@ auto binary::sink_rate_field(const solver_data_t& solver_data, location_2d_t sin //============================================================================= -binary::solution_t binary::advance(const solution_t& solution, const solver_data_t& solver_data, mara::unit_time dt) +binary::solution_t binary::advance(const solution_t& solution, const solver_data_t& solver_data, mara::unit_time dt, bool safe_mode) { - - - /* - * @brief An operator on arrays of sequences: takes a single component - * of a sequence and returns an array. - * - * @param[in] component The component to take - * - * @return An array whose value type is the sequence value type - */ - auto component = [] (std::size_t component) - { - return nd::map([component] (auto p) { return p[component]; }); - }; - - /* * @brief Extend each block in a given tree of cell-wise values, with * two guard zones on the given axis. @@ -103,30 +94,14 @@ binary::solution_t binary::advance(const solution_t& solution, const solver_data { return tree.indexes().map([tree, axis] (auto index) { - auto C = mara::get_cell_block(tree, index); - auto L = mara::get_cell_block(tree, index.prev_on(axis)) | nd::select_final(2, axis); - auto R = mara::get_cell_block(tree, index.next_on(axis)) | nd::select_first(2, axis); + auto C = tree.at(index); + auto L = mara::get_cell_block(tree, index.prev_on(axis), mara::compose(nd::to_shared(), nd::select_final(2, axis))); + auto R = mara::get_cell_block(tree, index.next_on(axis), mara::compose(nd::to_shared(), nd::select_first(2, axis))); return L | nd::concat(C).on_axis(axis) | nd::concat(R).on_axis(axis); }, tree_launch); }; - /* - * @brief Return an array of areas dx * dy from the given vertex - * locations. - * - * @param[in] vertices An array of vertices - * - * @return A new array - */ - auto area_from_vertices = [component] (auto vertices) - { - auto dx = vertices | component(0) | nd::difference_on_axis(0) | nd::midpoint_on_axis(1); - auto dy = vertices | component(1) | nd::difference_on_axis(1) | nd::midpoint_on_axis(0); - return dx * dy; - }; - - /* * @brief Apply a piecewise linear extrapolation, along the given axis, * to the cells in each array of a tree. @@ -135,7 +110,7 @@ binary::solution_t binary::advance(const solution_t& solution, const solver_data * * @return A function that operates on trees */ - auto extrapolate = [plm_theta=solver_data.plm_theta] (std::size_t axis) + auto extrapolate = [plm_theta=safe_mode ? 0.0 : solver_data.plm_theta] (std::size_t axis) { return [plm_theta, axis] (auto P) { @@ -182,8 +157,13 @@ binary::solution_t binary::advance(const solution_t& solution, const solver_data // Minor helper functions //========================================================================= auto evaluate = nd::to_shared(); - auto force_to_source_terms = [] (force_2d_t f) { return mara::iso2d::flow_t{0.0, f[0].value, f[1].value}; }; - auto recover_primitive = std::bind(mara::iso2d::recover_primitive, std::placeholders::_1, 0.0); + auto force_to_source_terms = [] (force_2d_t f) + { + return mara::iso2d::conserved_t() / mara::make_time(1.0); + // return mara::iso2d::flow_t{0.0, f[0].value, f[1].value}; + // TODO + }; + auto recover_primitive = [] (auto&& u) { return mara::iso2d::recover_primitive(u); }; auto cross_prod_z = [] (auto r, auto f) { return r[0] * f[1] - r[1] * f[0]; }; @@ -199,24 +179,24 @@ binary::solution_t binary::advance(const solution_t& solution, const solver_data // Intermediate scheme data //========================================================================= auto v0 = solver_data.vertices; + auto dA = solver_data.cell_areas; auto u0 = solution.conserved; auto p0 = u0.map(nd::map(recover_primitive)).map(evaluate, tree_launch); - auto dA = v0.map(area_from_vertices).map(evaluate, tree_launch); - auto dx = v0.map([component] (auto v) { return v | component(0) | nd::difference_on_axis(0); }); - auto dy = v0.map([component] (auto v) { return v | component(1) | nd::difference_on_axis(1); }); + auto dx = v0.map([] (auto v) { return v | component<0>() | nd::difference_on_axis(0); }); + auto dy = v0.map([] (auto v) { return v | component<1>() | nd::difference_on_axis(1); }); auto fx = extend(p0, 0).map(extrapolate(0), tree_launch).map(intercell_flux(mara::iso2d::riemann_hlle, 0)) * dy; auto fy = extend(p0, 1).map(extrapolate(1), tree_launch).map(intercell_flux(mara::iso2d::riemann_hlle, 1)) * dx; auto lx = -fx.map(nd::difference_on_axis(0)); auto ly = -fy.map(nd::difference_on_axis(1)); - auto m0 = u0.map(component(0)) * dA; // cell masses + auto m0 = u0.map(component<0>()) * dA; // cell masses // Gravitational force, sink fields, and buffer zone source term //========================================================================= - auto fg1 = grav_vdot_field(solver_data, body1_pos, binary.body1.mass) * m0; - auto fg2 = grav_vdot_field(solver_data, body2_pos, binary.body2.mass) * m0; - auto ss1 = -u0 * sink_rate_field(solver_data, body1_pos) * dA; - auto ss2 = -u0 * sink_rate_field(solver_data, body2_pos) * dA; + auto fg1 = grav_vdot_field(solver_data, body1_pos, binary.body1.mass) * m0; + auto fg2 = grav_vdot_field(solver_data, body2_pos, binary.body2.mass) * m0; + auto ss1 = -u0 * sink_rate_field(solver_data, body1_pos) * dA; + auto ss2 = -u0 * sink_rate_field(solver_data, body2_pos) * dA; auto sg = (fg1 + fg2).map(nd::map(force_to_source_terms)); auto ss = (ss1 + ss2); auto bz = (solver_data.initial_conserved - u0) * solver_data.buffer_rate_field * dA; @@ -224,16 +204,37 @@ binary::solution_t binary::advance(const solution_t& solution, const solver_data // The updated conserved densities //========================================================================= - auto u1 = u0 + (lx + ly + ss + sg + bz) * dt / dA; + auto u1 = u0 + (lx + ly + ss + sg + bz) * dt / dA; + auto next_conserved = u1.map(evaluate, tree_launch); + + if (! safe_mode && (next_conserved.map(component<0>()) < 0.0).map(nd::any()).any()) + { + std::cout << "binary::advance (negative density; re-trying in safe mode)" << std::endl; + return advance(solution, solver_data, dt, true); + } // The total force on each component, Mdot's, Ldot's, and Edot's. //========================================================================= + auto xc = solver_data.cell_centers.map(component<0>()); + auto yc = solver_data.cell_centers.map(component<1>()); auto fg1_tot = -fg1.map(nd::sum(), tree_launch).sum(); auto fg2_tot = -fg2.map(nd::sum(), tree_launch).sum(); - auto ss1_tot = -ss1.map(component(0)).map(nd::sum(), tree_launch).sum(); - auto ss2_tot = -ss2.map(component(0)).map(nd::sum(), tree_launch).sum(); + auto ss1_tot = -ss1.map(component<0>()).map(nd::sum(), tree_launch).sum(); + auto ss2_tot = -ss2.map(component<0>()).map(nd::sum(), tree_launch).sum(); + auto px_accrete1_rate = -ss1.map(component<1>()); + auto py_accrete1_rate = -ss1.map(component<2>()); + auto px_accrete2_rate = -ss2.map(component<1>()); + auto py_accrete2_rate = -ss2.map(component<2>()); + auto px_ejection_rate = -bz.map(component<1>()); + auto py_ejection_rate = -bz.map(component<2>()); + auto m0_ejection_rate = -bz.map(component<0>()).map(nd::sum(), tree_launch).sum(); + auto lz_ejection_rate = (xc * py_ejection_rate - yc * px_ejection_rate).map(nd::sum(), tree_launch).sum(); + auto lz_accrete1_rate = (xc * py_accrete1_rate - yc * px_accrete1_rate).map(nd::sum(), tree_launch).sum(); + auto lz_accrete2_rate = (xc * py_accrete2_rate - yc * px_accrete2_rate).map(nd::sum(), tree_launch).sum(); + auto Mdot = mara::make_sequence(ss1_tot, ss2_tot); + auto Kdot = mara::make_sequence(lz_accrete1_rate, lz_accrete2_rate); auto Ldot = mara::make_sequence(cross_prod_z(body1_pos, fg1_tot), cross_prod_z(body2_pos, fg2_tot)); auto Edot = mara::make_sequence((fg1_tot * body1_vel).sum(), (fg2_tot * body2_vel).sum()); @@ -243,10 +244,14 @@ binary::solution_t binary::advance(const solution_t& solution, const solver_data return solution_t{ solution.time + dt, solution.iteration + 1, - u1.map(evaluate, tree_launch), - solution.mass_accreted_on + Mdot * dt, - solution.integrated_torque_on + Ldot * dt, - solution.work_done_on + Edot * dt, + next_conserved, + + solution.mass_accreted_on + Mdot * dt, + solution.angular_momentum_accreted_on + Kdot * dt, + solution.integrated_torque_on + Ldot * dt, + solution.work_done_on + Edot * dt, + solution.mass_ejected + m0_ejection_rate * dt, + solution.angular_momentum_ejected + lz_ejection_rate * dt, }; } @@ -260,9 +265,13 @@ binary::solution_t binary::solution_t::operator+(const solution_t& other) const time + other.time, iteration + other.iteration, (conserved + other.conserved).map(nd::to_shared(), tree_launch), - mass_accreted_on + other.mass_accreted_on, - integrated_torque_on + other.integrated_torque_on, - work_done_on + other.work_done_on, + + mass_accreted_on + other.mass_accreted_on, + angular_momentum_accreted_on + other.angular_momentum_accreted_on, + integrated_torque_on + other.integrated_torque_on, + work_done_on + other.work_done_on, + mass_ejected + other.mass_ejected, + angular_momentum_ejected + other.angular_momentum_ejected, }; } @@ -272,9 +281,13 @@ binary::solution_t binary::solution_t::operator*(mara::rational_number_t scale) time * scale.as_double(), iteration * scale, (conserved * scale.as_double()).map(nd::to_shared(), tree_launch), - mass_accreted_on * scale.as_double(), - integrated_torque_on * scale.as_double(), - work_done_on * scale.as_double(), + + mass_accreted_on * scale.as_double(), + angular_momentum_accreted_on * scale.as_double(), + integrated_torque_on * scale.as_double(), + work_done_on * scale.as_double(), + mass_ejected * scale.as_double(), + angular_momentum_ejected * scale.as_double(), }; } diff --git a/src/subprog_binary_solver_data.cpp b/src/subprog_binary_solver_data.cpp index 0c60fb6..7a03fda 100644 --- a/src/subprog_binary_solver_data.cpp +++ b/src/subprog_binary_solver_data.cpp @@ -5,17 +5,36 @@ +//============================================================================= +static auto component(std::size_t component) +{ + return nd::map([component] (auto p) { return p[component]; }); +}; + + + + //============================================================================= binary::solver_data_t binary::create_solver_data(const mara::config_t& run_config) { auto vertices = create_vertices(run_config); - - auto primitive = vertices.map([&run_config] (auto block) + auto cell_centers = vertices.map([] (auto block) { return block | nd::midpoint_on_axis(0) - | nd::midpoint_on_axis(1) - | nd::map(create_disk_profile(run_config)); + | nd::midpoint_on_axis(1); + }); + + auto cell_areas = vertices.map([] (auto block) + { + auto dx = block | component(0) | nd::difference_on_axis(0) | nd::midpoint_on_axis(1); + auto dy = block | component(1) | nd::difference_on_axis(1) | nd::midpoint_on_axis(0); + return dx * dy; + }); + + auto primitive = cell_centers.map([&run_config] (auto block) + { + return block | nd::map(create_disk_profile(run_config)); }); auto min_dx = vertices.map([] (auto block) @@ -54,10 +73,13 @@ binary::solver_data_t binary::create_solver_data(const mara::config_t& run_confi auto rc = (p[0] * p[0] + p[1] * p[1]).pow<1, 2>(); auto y = (tightness * (rc - r1)).scalar(); return buffer_rate * (1.0 + std::tanh(y)); - }) - | nd::to_shared(); + }); }); + auto initial_conserved = primitive + .map(nd::map(std::mem_fn(&mara::iso2d::primitive_t::to_conserved_per_area))) + .map(nd::to_shared()); + //========================================================================= auto result = solver_data_t(); @@ -69,11 +91,11 @@ binary::solver_data_t binary::create_solver_data(const mara::config_t& run_confi result.rk_order = run_config.get_int("rk_order"); result.recommended_time_step = std::min(min_dx, min_dy) / max_velocity * run_config.get_double("cfl_number"); result.binary_params = create_binary_params(run_config); - result.buffer_rate_field = buffer_rate_field; + result.buffer_rate_field = buffer_rate_field.map(nd::to_shared()); + result.cell_centers = cell_centers.map(nd::to_shared()); + result.cell_areas = cell_areas.map(nd::to_shared()); result.vertices = vertices; - result.initial_conserved = primitive - .map(nd::map(std::mem_fn(&mara::iso2d::primitive_t::to_conserved_per_area))) - .map(nd::to_shared()); + result.initial_conserved = initial_conserved; if (run_config.get_string("riemann") == "hlle") result.riemann_solver = riemann_solver_t::hlle; // else if (run_config.get_string("riemann") == "hllc") result.riemann_solver = riemann_solver_t::hllc; diff --git a/src/subprog_binary_v1.cpp b/src/subprog_binary_v1.cpp deleted file mode 100644 index 3ce0226..0000000 --- a/src/subprog_binary_v1.cpp +++ /dev/null @@ -1,893 +0,0 @@ -/** - ============================================================================== - Copyright 2019, Jonathan Zrake - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is furnished to do - so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - ============================================================================== -*/ -#include "app_compile_opts.hpp" -#if MARA_COMPILE_SUBPROGRAM_BINARY_V1 - - - - -#include -#include -#include "core_hdf5.hpp" -#include "core_ndarray.hpp" -#include "core_ndarray_ops.hpp" -#include "app_config.hpp" -#include "app_serialize.hpp" -#include "app_schedule.hpp" -#include "app_performance.hpp" -#include "app_subprogram.hpp" -#include "app_filesystem.hpp" -#include "app_parallel.hpp" -#include "physics_iso2d.hpp" -#include "model_two_body.hpp" -#define cfl_number 0.4 -#define density_floor 0.0 -#define sound_speed_squared 1e-4 - - - - -//============================================================================= -static auto config_template() -{ - return mara::make_config_template() - .item("restart", std::string()) - .item("outdir", "data") // directory where data products are written to - .item("cpi", 10.0) // checkpoint interval (orbits; chkpt.????.h5 - snapshot of app_state) - .item("dfi", 1.0) // diagnostic field interval (orbits; diagnostics.????.h5 - for plotting 2d solution data) - .item("tsi", 0.1) // time series interval (orbits) - .item("tfinal", 1.0) // simulation stop time (orbits) - .item("N", 256) // grid resolution (same in x and y) - .item("rk_order", 2) // time-stepping Runge-Kutta order: 1 or 2 - .item("reconstruct_method", "plm") // zone extrapolation method: pcm or plm - .item("plm_theta", 1.8) // plm theta parameter: [1.0, 2.0] - .item("riemann", "hllc") // riemann solver to use: hlle or hllc - .item("softening_radius", 0.1) // gravitational softening radius - .item("sink_radius", 0.1) // radius of mass (and momentum) subtraction region - .item("sink_rate", 1e2) // sink rate at the point masses (orbital angular frequency) - // .item("mach_number", 10.0) // not implemented yet - // .item("viscous_alpha", 0.1) // not implemented yet - .item("domain_radius", 6.0) // half-size of square domain - .item("separation", 1.0) // binary separation: 0.0 or 1.0 (zero emulates a single body) - .item("mass_ratio", 1.0) // binary mass ratio M2 / M1: (0.0, 1.0] - .item("eccentricity", 0.0) // orbital eccentricity: [0.0, 1.0) - .item("buffer_damping_rate", 10.0) // maximum rate of buffer zone, where solution is driven to initial state - .item("counter_rotate", 0); // retrograde disk option: 0 or 1 -} - - - - -namespace binary_v1 -{ - - - //========================================================================= - using location_2d_t = mara::arithmetic_sequence_t, 2>; - using velocity_2d_t = mara::arithmetic_sequence_t, 2>; - using acceleration_2d_t = mara::arithmetic_sequence_t, 2>; - using force_2d_t = mara::arithmetic_sequence_t, 2>; - - - //========================================================================= - enum class reconstruct_method_t - { - plm, - pcm, - }; - - - //========================================================================= - enum class riemann_solver_t - { - hlle, - hllc, - }; - - - //========================================================================= - struct solver_data_t - { - mara::unit_rate sink_rate; - mara::unit_length sink_radius; - mara::unit_length softening_radius; - mara::unit_time recommended_time_step; - - double plm_theta; - int rk_order; - reconstruct_method_t reconstruct_method; - riemann_solver_t riemann_solver; - mara::two_body_parameters_t binary_params; - - nd::shared_array, 1> x_vertices; - nd::shared_array, 1> y_vertices; - nd::shared_array, 2> buffer_damping_rate_field; - nd::shared_array initial_conserved_field; - }; - - - //========================================================================= - struct solution_t - { - mara::unit_time time = 0.0; - mara::rational_number_t iteration = 0; - nd::shared_array conserved; - - //===================================================================== - solution_t operator+(const solution_t& other) const - { - auto evaluate = mara::evaluate_on(); - - return { - time + other.time, - iteration + other.iteration, - conserved + other.conserved | evaluate, - }; - } - solution_t operator*(mara::rational_number_t scale) const - { - auto evaluate = mara::evaluate_on(); - - return { - time * scale.as_double(), - iteration * scale, - conserved * scale.as_double() | evaluate, - }; - } - }; - - - //========================================================================= - struct app_state_t - { - solution_t solution_state; - solver_data_t solver_data; - mara::schedule_t schedule; - mara::config_t run_config; - }; - - - //========================================================================= - struct diagnostic_fields_t - { - mara::unit_time time; - location_2d_t position_of_mass1; - location_2d_t position_of_mass2; - nd::shared_array, 1> x_vertices; - nd::shared_array, 1> y_vertices; - nd::shared_array sigma; - nd::shared_array phi_velocity; - nd::shared_array radial_velocity; - }; - - - //========================================================================= - templateauto cell_surface_area(const VertexArrayType& xv, const VertexArrayType& yv); - templateauto cell_center_cartprod(const VertexArrayType& xv, const VertexArrayType& yv); - auto initial_disk_profile_ring (const mara::config_t& cfg); - auto initial_disk_profile_tang17(const mara::config_t& cfg); - - auto diagnostic_fields(const solution_t& state, const solver_data_t& solver_data); - auto gravitational_acceleration_field(mara::unit_time time, const solver_data_t& solver_data); - auto sink_rate_field(mara::unit_time time, const solver_data_t& solver_data); - auto buffer_damping_rate_at_position(const mara::config_t& cfg); - auto estimate_gradient_plm(double plm_theta); - auto recover_primitive(const mara::iso2d::conserved_per_area_t& conserved); - auto advance(const solution_t& state, const solver_data_t& solver_data, mara::unit_time dt); - auto next_solution(const solution_t& state, const solver_data_t& solver_data); - - - //========================================================================= - void write_solution(h5::Group&& group, const solution_t& state); - void write_diagnostic_fields(h5::Group&& group, const diagnostic_fields_t& diagnostics); - void write_checkpoint(const app_state_t& state, std::string outdir); - void write_diagnostics(const app_state_t& state, std::string outdir); - void write_time_series(const app_state_t& state, std::string outdir); - - - //========================================================================= - void print_run_loop_message(const app_state_t& state, mara::perf_diagnostics_t perf); - void prepare_filesystem(const mara::config_t& cfg); - - - //========================================================================= - auto read_solution(h5::Group&& group); - auto new_solution(const mara::config_t& cfg); - auto create_solution(const mara::config_t& run_config); - auto create_solver_data(const mara::config_t& cfg); - - auto create_app_state(const mara::config_t& run_config); - auto create_app_state_next_function(const solver_data_t& solver_data); - auto simulation_should_continue(const app_state_t& state); - auto run_tasks(const app_state_t& state); -} - -using namespace binary_v1; - - - - -/** - * @brief Initial conditions from Tang+ (2017) MNRAS 469, 4258 - * - * @param[in] cfg The run config - * - * @return A function that maps (x, y) coordinates to primitive variable - * states - * - * @note This should be a time-indepdent solution of flow in a thin disk, - * with alpha viscosity and a single point mass M located at the - * origin. - */ -auto binary_v1::initial_disk_profile_tang17(const mara::config_t& cfg) -{ - auto softening_radius = cfg.get_double("softening_radius"); - auto mach_number = cfg.get_double("mach_number"); - auto viscous_alpha = cfg.get_double("viscous_alpha"); - auto counter_rotate = cfg.get_int("counter_rotate"); - - return [=] (auto x_length, auto y_length) - { - auto GM = 1.0; - auto x = x_length.value; - auto y = y_length.value; - - auto rs = softening_radius; - auto r0 = 2.5; - auto sigma0 = GM; - auto r2 = x * x + y * y; - auto r = std::sqrt(r2); - auto cavity_xsi = 10.0; - auto cavity_cutoff = std::max(std::exp(-std::pow(r / r0, -cavity_xsi)), 1e-6); - auto phi = -GM * std::pow(r2 + rs * rs, -0.5); - auto ag = -GM * std::pow(r2 + rs * rs, -1.5) * r; - auto cs2 = -phi / mach_number / mach_number; - auto cs2_deriv = ag / mach_number / mach_number; - auto sigma = sigma0 * std::pow((r + rs) / r0, -0.5) * cavity_cutoff; - auto sigma_deriv = sigma0 * std::pow((r + rs) / r0, -1.5) * -0.5 / r0; - auto dp_dr = cs2 * sigma_deriv + cs2_deriv * sigma; - auto omega2 = r < r0 ? GM / (4 * r0) : -ag / r + dp_dr / (sigma * r); - auto vp = (counter_rotate ? -1 : 1) * r * std::sqrt(omega2); - auto h0 = r / mach_number; - auto nu = viscous_alpha * std::sqrt(cs2) * h0; // viscous_alpha * cs * h0 - auto vr = -(3.0 / 2.0) * nu / (r + rs); // inward drift velocity (CHECK) - auto vx = vp * (-y / r) + vr * (x / r); - auto vy = vp * ( x / r) + vr * (y / r); - - return mara::iso2d::primitive_t() - .with_sigma(sigma) - .with_velocity_x(vx) - .with_velocity_y(vy); - }; -} - -auto binary_v1::initial_disk_profile_ring(const mara::config_t& cfg) -{ - auto softening_radius = cfg.get_double("softening_radius"); - auto counter_rotate = cfg.get_int("counter_rotate"); - - return [=] (auto x_length, auto y_length) - { - auto GM = 1.0; - auto x = x_length.value; - auto y = y_length.value; - auto rs = softening_radius; - auto rc = 2.5; - auto r2 = x * x + y * y; - auto r = std::sqrt(r2); - auto sigma = std::exp(-std::pow(r - rc, 2) / rc / 2); - auto ag = -GM * std::pow(r2 + rs * rs, -1.5) * r; - auto omega2 = -ag / r; - auto vp = (counter_rotate ? -1 : 1) * r * std::sqrt(omega2); - auto vx = vp * (-y / r); - auto vy = vp * ( x / r); - - return mara::iso2d::primitive_t() - .with_sigma(sigma) - .with_velocity_x(vx) - .with_velocity_y(vy); - }; -} - - - - -//============================================================================= -template -auto binary_v1::cell_surface_area(const VertexArrayType& xv, const VertexArrayType& yv) -{ - auto dx = xv | nd::difference_on_axis(0); - auto dy = yv | nd::difference_on_axis(0); - return nd::cartesian_product(dx, dy) | nd::apply(std::multiplies<>()); -} - -template -auto binary_v1::cell_center_cartprod(const VertexArrayType& xv, const VertexArrayType& yv) -{ - auto xc = xv | nd::midpoint_on_axis(0); - auto yc = yv | nd::midpoint_on_axis(0); - return nd::cartesian_product(xc, yc); -} - -auto binary_v1::buffer_damping_rate_at_position(const mara::config_t& cfg) -{ - return [cfg] (mara::unit_length x, mara::unit_length y) - { - constexpr double tightness = 3.0; - auto r = std::sqrt(std::pow(x.value, 2) + std::pow(y.value, 2)); - auto r1 = cfg.get_double("domain_radius"); - return mara::make_rate(1.0 + std::tanh(tightness * (r - r1))) * cfg.get_double("buffer_damping_rate"); - }; -} - -auto binary_v1::gravitational_acceleration_field(mara::unit_time time, const solver_data_t& solver_data) -{ - auto binary = mara::compute_two_body_state(solver_data.binary_params, time.value); - auto softening_radius = solver_data.softening_radius; - auto accel = [softening_radius] (const mara::point_mass_t& body, auto x, auto y) - { - auto field_point = location_2d_t { x, y }; - auto mass_location = location_2d_t { body.position_x, body.position_y }; - - auto G = mara::dimensional_value_t<3, -1, -2, double>(1.0); - auto M = mara::make_mass(body.mass); - auto dr = field_point - mass_location; - auto dr2 = dr[0] * dr[0] + dr[1] * dr[1]; - auto rs2 = softening_radius * softening_radius; - return -dr / (dr2 + rs2).pow<3, 2>() * G * M; - }; - auto acceleration = [binary, accel] (auto x, auto y) - { - return accel(binary.body1, x, y) + accel(binary.body2, x, y); - }; - return cell_center_cartprod(solver_data.x_vertices, solver_data.y_vertices) | nd::apply(acceleration); -} - -auto binary_v1::sink_rate_field(mara::unit_time time, const solver_data_t& solver_data) -{ - auto binary = mara::compute_two_body_state(solver_data.binary_params, time.value); - auto sink = [binary, sink_radius=solver_data.sink_radius, sink_rate=solver_data.sink_rate] (auto x, auto y) - { - auto dx1 = x - mara::make_length(binary.body1.position_x); - auto dy1 = y - mara::make_length(binary.body1.position_y); - auto dx2 = x - mara::make_length(binary.body2.position_x); - auto dy2 = y - mara::make_length(binary.body2.position_y); - - auto s2 = sink_radius * sink_radius; - auto a2 = (dx1 * dx1 + dy1 * dy1) / s2 / 2.0; - auto b2 = (dx2 * dx2 + dy2 * dy2) / s2 / 2.0; - - return sink_rate * 0.5 * (std::exp(-a2) + std::exp(-b2)); - }; - return cell_center_cartprod(solver_data.x_vertices, solver_data.y_vertices) | nd::apply(sink); -} - -auto binary_v1::estimate_gradient_plm(double plm_theta) -{ - return [plm_theta] ( - const mara::iso2d::primitive_t& pl, - const mara::iso2d::primitive_t& p0, - const mara::iso2d::primitive_t& pr) - { - using std::min; - using std::fabs; - auto min3abs = [] (auto a, auto b, auto c) { return min(min(fabs(a), fabs(b)), fabs(c)); }; - auto sgn = [] (auto x) { return std::copysign(1, x); }; - - double th = plm_theta; - auto result = mara::iso2d::primitive_t(); - - for (std::size_t i = 0; i < 3; ++i) - { - auto a = th * (p0[i] - pl[i]); - auto b = 0.5 * (pr[i] - pl[i]); - auto c = th * (pr[i] - p0[i]); - result[i] = 0.25 * std::fabs(sgn(a) + sgn(b)) * (sgn(a) + sgn(c)) * min3abs(a, b, c); - } - return result; - }; -} - -auto binary_v1::recover_primitive(const mara::iso2d::conserved_per_area_t& conserved) -{ - return mara::iso2d::recover_primitive(conserved, density_floor); -} - -auto binary_v1::advance(const solution_t& state, const solver_data_t& solver_data, mara::unit_time dt) -{ - auto force_to_source_terms = [] (force_2d_t v) - { - return mara::iso2d::flow_t {{0.0, v[0].value, v[1].value}}; - }; - - auto extrapolate_pcm = [] (std::size_t axis) - { - return [axis] (auto P) - { - auto L = nd::select_axis(axis).from(0).to(1).from_the_end(); - auto R = nd::select_axis(axis).from(1).to(0).from_the_end(); - return nd::zip(P | L, P | R); - }; - }; - - auto extrapolate_plm = [plm_theta=solver_data.plm_theta] (std::size_t axis) - { - return [plm_theta, axis] (auto P) - { - auto evaluate = mara::evaluate_on(); - - auto L = nd::select_axis(axis).from(0).to(1).from_the_end(); - auto R = nd::select_axis(axis).from(1).to(0).from_the_end(); - auto G = P - | nd::zip_adjacent3_on_axis(axis) - | nd::apply(estimate_gradient_plm(plm_theta)) - | nd::extend_zeros(axis) - | evaluate; - - return nd::zip( - (P | L) + (G | L) * 0.5, - (P | R) - (G | R) * 0.5); - }; - }; - - auto intercell_flux = [] (auto riemann_solver, std::size_t axis) - { - return [axis, riemann_solver] (auto left_and_right_states) - { - using namespace std::placeholders; - auto nh = mara::unit_vector_t::on_axis(axis); - auto riemann = std::bind(riemann_solver, _1, _2, sound_speed_squared, sound_speed_squared, nh); - return left_and_right_states | nd::apply(riemann); - }; - }; - - auto advance_with = [&] (auto riemann_solver, auto extrapolate) - { - auto fhat_x = intercell_flux(riemann_solver, 0); - auto fhat_y = intercell_flux(riemann_solver, 1); - auto evaluate = mara::evaluate_on(); - - auto dA = cell_surface_area(solver_data.x_vertices, solver_data.y_vertices); - auto u0 = state.conserved; - auto p0 = u0 / dA | nd::map(recover_primitive) | evaluate; - auto dx = nd::get<0>(nd::cartesian_product(solver_data.x_vertices | nd::difference_on_axis(0), solver_data.y_vertices)); - auto dy = nd::get<1>(nd::cartesian_product(solver_data.x_vertices, solver_data.y_vertices | nd::difference_on_axis(0))); - auto cell_mass = u0 | nd::map([] (auto u) { return u[0]; }); - - auto lx = p0 | nd::extend_periodic_on_axis(0) | extrapolate(0) | fhat_x | nd::multiply(-dy) | nd::difference_on_axis(0); - auto ly = p0 | nd::extend_periodic_on_axis(1) | extrapolate(1) | fhat_y | nd::multiply(-dx) | nd::difference_on_axis(1); - auto sg = gravitational_acceleration_field(state.time, solver_data) | nd::multiply(cell_mass) | nd::map(force_to_source_terms); - auto ss = -u0 * sink_rate_field(state.time, solver_data); - auto bz = (solver_data.initial_conserved_field - u0) * solver_data.buffer_damping_rate_field; - auto u1 = u0 + (lx + ly + sg + ss + bz) * dt; - return u1 | evaluate; - }; - - auto next_state = solution_t(); - next_state.iteration = state.iteration + 1; - next_state.time = state.time + dt; - - switch (solver_data.reconstruct_method) - { - case reconstruct_method_t::pcm: - switch (solver_data.riemann_solver) - { - case riemann_solver_t::hlle: next_state.conserved = advance_with(mara::iso2d::riemann_hlle, extrapolate_pcm); break; - case riemann_solver_t::hllc: next_state.conserved = advance_with(mara::iso2d::riemann_hllc, extrapolate_pcm); break; - } - break; - case reconstruct_method_t::plm: - switch (solver_data.riemann_solver) - { - case riemann_solver_t::hlle: next_state.conserved = advance_with(mara::iso2d::riemann_hlle, extrapolate_plm); break; - case riemann_solver_t::hllc: next_state.conserved = advance_with(mara::iso2d::riemann_hllc, extrapolate_plm); break; - } - break; - } - return next_state; -} - -auto binary_v1::next_solution(const solution_t& state, const solver_data_t& solver_data) -{ - auto dt = solver_data.recommended_time_step; - auto s0 = state; - - switch (solver_data.rk_order) - { - case 1: - { - return advance(s0, solver_data, dt); - } - case 2: - { - auto b0 = mara::make_rational(1, 2); - auto s1 = advance(s0, solver_data, dt); - auto s2 = advance(s1, solver_data, dt); - return s0 * b0 + s2 * (1 - b0); - } - } - throw std::invalid_argument("binary_v1::next_solution"); -} - - - - -//============================================================================= -auto binary_v1::diagnostic_fields(const solution_t& state, const solver_data_t& solver_data) -{ - auto dA = cell_surface_area(solver_data.x_vertices, solver_data.y_vertices); - auto [xc, yc] = nd::unzip(cell_center_cartprod(solver_data.x_vertices, solver_data.y_vertices)); - auto rc = xc * xc + yc * yc | nd::map([] (auto r2) { return mara::make_length(std::sqrt(r2.value)); }); - auto rhat_x = xc / rc; - auto rhat_y = yc / rc; - auto phat_x = -yc / rc; - auto phat_y = xc / rc; - auto u = state.conserved; - auto p = u / dA | nd::map(recover_primitive); - auto sigma = p | nd::map(std::mem_fn(&mara::iso2d::primitive_t::sigma)); - auto vx = p | nd::map(std::mem_fn(&mara::iso2d::primitive_t::velocity_x)); - auto vy = p | nd::map(std::mem_fn(&mara::iso2d::primitive_t::velocity_y)); - auto binary = mara::compute_two_body_state(solver_data.binary_params, state.time.value); - auto evaluate = mara::evaluate_on(); - - auto result = diagnostic_fields_t(); - result.time = state.time; - result.x_vertices = solver_data.x_vertices; - result.y_vertices = solver_data.y_vertices; - result.sigma = sigma | evaluate; - result.radial_velocity = vx * rhat_x + vy * rhat_y | evaluate; - result.phi_velocity = vx * phat_x + vy * phat_y | evaluate; - result.position_of_mass1 = location_2d_t {{ binary.body1.position_x, binary.body1.position_y }}; - result.position_of_mass2 = location_2d_t {{ binary.body2.position_x, binary.body2.position_y }}; - - return result; -} - - - - -//============================================================================= -void binary_v1::write_solution(h5::Group&& group, const solution_t& state) -{ - group.write("time", state.time); - group.write("iteration", state.iteration); - group.write("conserved", state.conserved); -} - -void binary_v1::write_diagnostic_fields(h5::Group&& group, const diagnostic_fields_t& diagnostics) -{ - group.write("time", diagnostics.time); - group.write("x_vertices", diagnostics.x_vertices); - group.write("y_vertices", diagnostics.y_vertices); - group.write("sigma", diagnostics.sigma); - group.write("phi_velocity", diagnostics.phi_velocity); - group.write("radial_velocity", diagnostics.radial_velocity); - group.write("position_of_mass1", diagnostics.position_of_mass1); - group.write("position_of_mass2", diagnostics.position_of_mass2); -} - -void binary_v1::write_checkpoint(const app_state_t& state, std::string outdir) -{ - auto count = state.schedule.num_times_performed("write_checkpoint"); - auto file = h5::File(mara::filesystem::join(outdir, mara::create_numbered_filename("chkpt", count, "h5")), "w"); - - write_solution(file.require_group("solution"), state.solution_state); - mara::write_schedule(file.require_group("schedule"), state.schedule); - mara::write_config(file.require_group("run_config"), state.run_config); - - std::printf("write checkpoint: %s\n", file.filename().data()); -} - -void binary_v1::write_diagnostics(const app_state_t& state, std::string outdir) -{ - auto count = state.schedule.num_times_performed("write_diagnostics"); - auto file = h5::File(mara::filesystem::join(outdir, mara::create_numbered_filename("diagnostics", count, "h5")), "w"); - auto diagnostics = diagnostic_fields(state.solution_state, state.solver_data); - - write_diagnostic_fields(file.open_group("/"), diagnostics); - - // for (auto item : diagnostics.time_series_data) - // { - // file.write(item.first, item.second); - // } - - std::printf("write diagnostics: %s\n", file.filename().data()); -} - -void binary_v1::write_time_series(const app_state_t& state, std::string outdir) -{ - // auto file = h5::File(mara::filesystem::join({outdir, "time_series.h5"}), "r+"); - // auto current_size = state.schedule.num_times_performed("write_time_series"); - // auto target_space = h5::hyperslab_t{{std::size_t(current_size)}, {1}, {1}, {1}}; - - // for (auto item : compute_time_series_data(state.solution_state)) - // { - // auto dataset = file.open_dataset(item.first); - // dataset.set_extent(current_size + 1); - // dataset.write(item.second, dataset.get_space().select(target_space)); - // } -} - - - - -//============================================================================= -auto binary_v1::create_solver_data(const mara::config_t& cfg) -{ - auto nx = cfg.get_int("N"); - auto ny = cfg.get_int("N"); - auto R0 = cfg.get_double("domain_radius"); - auto xv = nd::linspace(-R0, R0, nx + 1) | nd::map(mara::make_length); - auto yv = nd::linspace(-R0, R0, ny + 1) | nd::map(mara::make_length); - - auto u = cell_center_cartprod(xv, yv) - | nd::apply(initial_disk_profile_ring(cfg)) - | nd::map(std::mem_fn(&mara::iso2d::primitive_t::to_conserved_per_area)) - | nd::multiply(cell_surface_area(xv, yv)); - - auto b = cell_center_cartprod(xv, yv) - | nd::apply(buffer_damping_rate_at_position(cfg)); - - auto dA = cell_surface_area(xv, yv); - auto p0 = u / dA | nd::map(recover_primitive); - auto v0 = p0 | nd::map(std::mem_fn(&mara::iso2d::primitive_t::velocity_magnitude)); - auto dx = mara::make_length(2 * R0) / std::max(nx, ny); - auto dt = dx / nd::max(v0) * 0.5 * cfl_number; - - auto result = solver_data_t(); - result.sink_rate = cfg.get_double("sink_rate"); - result.sink_radius = cfg.get_double("sink_radius"); - result.softening_radius = cfg.get_double("softening_radius"); - result.plm_theta = cfg.get_double("plm_theta"); - result.rk_order = cfg.get_int("rk_order"); - result.recommended_time_step = dt; - - result.x_vertices = xv | nd::to_shared(); - result.y_vertices = yv | nd::to_shared(); - result.initial_conserved_field = u | nd::to_shared(); - result.buffer_damping_rate_field = b | nd::to_shared(); - - if (cfg.get_string("riemann") == "hlle") result.riemann_solver = riemann_solver_t::hlle; - else if (cfg.get_string("riemann") == "hllc") result.riemann_solver = riemann_solver_t::hllc; - else throw std::invalid_argument("invalid riemann solver '" + cfg.get_string("riemann") + "', must be hlle or hllc"); - - if (cfg.get_string("reconstruct_method") == "pcm") result.reconstruct_method = reconstruct_method_t::pcm; - else if (cfg.get_string("reconstruct_method") == "plm") result.reconstruct_method = reconstruct_method_t::plm; - else throw std::invalid_argument("invalid reconstruct_method '" + cfg.get_string("reconstruct_method") + "', must be plm or pcm"); - - result.binary_params.total_mass = 1.0; - result.binary_params.separation = cfg.get_double("separation"); - result.binary_params.mass_ratio = cfg.get_double("mass_ratio"); - result.binary_params.eccentricity = cfg.get_double("eccentricity"); - - return result; -} - -auto binary_v1::read_solution(h5::Group&& group) -{ - return solution_t(); // IMPLEMENT READING SOLUTION FROM CHECKPOINT -} - -auto binary_v1::new_solution(const mara::config_t& cfg) -{ - auto state = solution_t(); - state.time = 0.0; - state.iteration = 0; - state.conserved = create_solver_data(cfg).initial_conserved_field; - return state; -} - -auto binary_v1::create_solution(const mara::config_t& run_config) -{ - auto restart = run_config.get("restart"); - return restart.empty() - ? new_solution(run_config) - : read_solution(h5::File(restart, "r").open_group("solution")); -} - - - - -//============================================================================= -static auto new_schedule(const mara::config_t& run_config) -{ - auto schedule = mara::schedule_t(); - schedule.create_and_mark_as_due("write_checkpoint"); - schedule.create_and_mark_as_due("write_diagnostics"); - schedule.create_and_mark_as_due("write_time_series"); - return schedule; -} - -static auto create_schedule(const mara::config_t& run_config) -{ - auto restart = run_config.get("restart"); - return restart.empty() - ? new_schedule(run_config) - : mara::read_schedule(h5::File(restart, "r").open_group("schedule")); -} - -static auto next_schedule(const mara::schedule_t& schedule, const mara::config_t& run_config, double time) -{ - auto next_schedule = schedule; - auto cpi = run_config.get_double("cpi") * 2 * M_PI; - auto dfi = run_config.get_double("dfi") * 2 * M_PI; - auto tsi = run_config.get_double("tsi") * 2 * M_PI; - - if (time - schedule.last_performed("write_checkpoint") >= cpi) next_schedule.mark_as_due("write_checkpoint", cpi); - if (time - schedule.last_performed("write_diagnostics") >= dfi) next_schedule.mark_as_due("write_diagnostics", dfi); - if (time - schedule.last_performed("write_time_series") >= tsi) next_schedule.mark_as_due("write_time_series", tsi); - - return next_schedule; -} - - - - -//============================================================================= -static auto new_run_config(const mara::config_string_map_t& args) -{ - return config_template().create().update(args); -} - -static auto create_run_config(int argc, const char* argv[]) -{ - auto args = mara::argv_to_string_map(argc, argv); - return args.count("restart") - ? config_template() - .create() - .update(mara::read_config(h5::File(args.at("restart"), "r").open_group("run_config"))) - .update(args) - : new_run_config(args); -} - - - - -//============================================================================= -auto binary_v1::create_app_state(const mara::config_t& run_config) -{ - auto state = app_state_t(); - state.run_config = run_config; - state.solution_state = create_solution(run_config); - state.schedule = create_schedule(run_config); - state.solver_data = create_solver_data(run_config); - return state; -} - -auto binary_v1::create_app_state_next_function(const solver_data_t& solver_data) -{ - return [solver_data] (const app_state_t& state) - { - auto next_state = state; - next_state.solution_state = next_solution(state.solution_state, solver_data); - next_state.schedule = next_schedule(state.schedule, state.run_config, state.solution_state.time.value); - return next_state; - }; -} - -auto binary_v1::simulation_should_continue(const app_state_t& state) -{ - auto orbits = state.solution_state.time / (2 * M_PI); - return orbits < state.run_config.get("tfinal"); -} - -auto binary_v1::run_tasks(const app_state_t& state) -{ - auto next_state = state; - auto outdir = state.run_config.get_string("outdir"); - - if (state.schedule.is_due("write_checkpoint")) - { - write_checkpoint(state, outdir); - next_state.schedule.mark_as_completed("write_checkpoint"); - } - if (state.schedule.is_due("write_diagnostics")) - { - write_diagnostics(state, outdir); - next_state.schedule.mark_as_completed("write_diagnostics"); - } - if (state.schedule.is_due("write_time_series")) - { - write_time_series(state, outdir); - next_state.schedule.mark_as_completed("write_time_series"); - } - return next_state; -} - - - - -//============================================================================= -void binary_v1::print_run_loop_message(const app_state_t& state, mara::perf_diagnostics_t perf) -{ - auto kzps = - state.solver_data.x_vertices.size() * - state.solver_data.y_vertices.size() / perf.execution_time_ms; - - std::printf("[%04d] orbits=%3.7lf kzps=%3.2lf\n", - state.solution_state.iteration.as_integral(), - state.solution_state.time.value / (2 * M_PI), kzps); -} - -void binary_v1::prepare_filesystem(const mara::config_t& cfg) -{ - if (cfg.get_string("restart").empty()) - { - auto outdir = cfg.get_string("outdir"); - mara::filesystem::require_dir(outdir); - - auto file = h5::File(mara::filesystem::join(outdir, "time_series.h5"), "w"); - auto plist = h5::PropertyList::dataset_create().set_chunk(1000); - auto space = h5::Dataspace::unlimited(0); - - // for (auto column_name : get_time_series_column_names()) - // { - // file.require_dataset(column_name, h5::Datatype::native_double(), space, plist); - // } - mara::write_config(file.require_group("run_config"), cfg); - } -} - - - - -//============================================================================= -class subprog_binary_v1 : public mara::sub_program_t -{ -public: - - int main(int argc, const char* argv[]) override - { - auto run_config = create_run_config(argc, argv); - auto state = create_app_state(run_config); - auto next = create_app_state_next_function(state.solver_data); - auto perf = mara::perf_diagnostics_t(); - - prepare_filesystem(run_config); - mara::pretty_print(std::cout, "config", run_config); - state = run_tasks(state); - - while (simulation_should_continue(state)) - { - std::tie(state, perf) = mara::time_execution(mara::compose(run_tasks, next), state); - print_run_loop_message(state, perf); - } - - run_tasks(next(state)); - return 0; - } - - std::string name() const override - { - return "binary_v1"; - } -}; - -std::unique_ptr make_subprog_binary_v1() -{ - return std::make_unique(); -} - -#endif // MARA_COMPILE_SUBPROGRAM_BINARY_V1 diff --git a/tools/plot_binary.py b/tools/plot_binary.py index 123e768..72bf89b 100755 --- a/tools/plot_binary.py +++ b/tools/plot_binary.py @@ -2,6 +2,7 @@ + import argparse import numpy as np import h5py @@ -9,7 +10,56 @@ + +def moving_average(a, window_size=10): + """ + @brief Return the moving average of an array, with the given window + size. + + @param a The array + @param window_size The window size to use + + @return The window-averaged array + """ + n = window_size + ret = np.cumsum(a, dtype=float) + ret[n:] = ret[n:] - ret[:-n] + return ret[n - 1:] / n + + + + +def plot_moving_average(ax, x, y, window_size=100, avg_only=False, c=None, **kwargs): + """ + @brief Wrapper for ax.plot, where the exact values of x and y are + plotted with a lower alpha, and the moving averages are plotted + + @param ax The axis instance to plot on + @param x The x values + @param y The y values + @param window_size The window size to use in the moving average + @param avg_only Plot only the moving average if True + @param c The color + @param kwargs Keyword args passed to the plot of moving averages + + @return The result of ax.plot + """ + + if not avg_only: + ax.plot(x, y, c=c, lw=1.0, alpha=0.5) + return ax.plot(moving_average(x, window_size), moving_average(y, window_size), **kwargs) + + + + def get_ranges(args): + """ + @brief Return the vmin and vmax keywords for fields. + + @param args The arguments (argparse result) + + @return The vmin/vmax values + """ return dict( sigma_range=eval(args.sigma, dict(default=[ -2.0, 0.0])), vr_range =eval(args.vr, dict(default=[ -0.5, 0.5])), @@ -17,6 +67,7 @@ def get_ranges(args): + def plot_single_block(ax, h5_verts, h5_values, edges=False, **kwargs): X = h5_verts[...][:,:,0] Y = h5_verts[...][:,:,1] @@ -26,12 +77,13 @@ def plot_single_block(ax, h5_verts, h5_values, edges=False, **kwargs): Xb = X[::X.shape[0]//2, ::X.shape[1]//2] Yb = Y[::Y.shape[0]//2, ::Y.shape[1]//2] Zb = np.zeros_like(Xb + Yb) - ax.pcolormesh(Xb, Yb, Zb, edgecolor=(1.0, 1.0, 1.0, 0.3)) + ax.pcolormesh(Xb, Yb, Zb, edgecolor=(1.0, 0.0, 1.0, 0.3)) return ax.pcolormesh(X, Y, Z, **kwargs) + def plot_single_file_with_vel( fig, filename, @@ -75,6 +127,7 @@ def plot_single_file_with_vel( + def plot_single_file_sigma_only( fig, filename, @@ -108,6 +161,7 @@ def plot_single_file_sigma_only( + def make_movie_impl(args, plot_fn, figsize=[16, 6]): from matplotlib.animation import FFMpegWriter @@ -120,21 +174,24 @@ def make_movie_impl(args, plot_fn, figsize=[16, 6]): with writer.saving(fig, args.output, dpi): for filename in args.filenames: print(filename) - plot_fn(fig, filename, edges=args.edges, depth=args.depth, **get_ranges(args)) + fig = plot_fn(fig, filename, edges=args.edges, depth=args.depth, **get_ranges(args)) writer.grab_frame() fig.clf() + def raise_figure_windows_impl(args, plot_fn, figsize=[16, 6]): for filename in args.filenames: print(filename) fig = plt.figure(figsize=figsize) plot_fn(fig, filename, edges=args.edges, depth=args.depth, **get_ranges(args)) + fig.suptitle(filename) plt.show() + def make_movie(args): if args.with_vel: make_movie_impl(args, plot_single_file_with_vel, figsize=[16, 6]) @@ -143,6 +200,7 @@ def make_movie(args): + def raise_figure_windows(args): if args.with_vel: raise_figure_windows_impl(args, plot_single_file_with_vel, figsize=[16, 6]) @@ -151,17 +209,21 @@ def raise_figure_windows(args): + def unzip_time_series(h5_time_series): ts = h5_time_series[:] return {k:[s[i] for s in ts] for i, k in enumerate(ts.dtype.names)} + def time_series(args): - fig = plt.figure(figsize=[15, 8]) - ax1 = fig.add_subplot(2, 1, 1) - ax2 = fig.add_subplot(2, 1, 2) + fig = plt.figure(figsize=[15, 9]) + ax1 = fig.add_subplot(4, 1, 1) + ax2 = fig.add_subplot(4, 1, 2) + ax3 = fig.add_subplot(4, 1, 3) + ax4 = fig.add_subplot(4, 1, 4) colors = plt.cm.viridis(np.linspace(0.3, 0.7, len(args.filenames))) @@ -170,10 +232,18 @@ def time_series(args): ts = unzip_time_series(h5f['time_series']) t = np.array([s / 2 / np.pi for s in ts['time']]) + Md = np.array([s for s in ts['disk_mass']]) + Me = np.array([s for s in ts['mass_ejected']]) M1 = np.array([s[0] for s in ts['mass_accreted_on']]) M2 = np.array([s[1] for s in ts['mass_accreted_on']]) + + Ld = np.array([s for s in ts['disk_angular_momentum']]) + Le = np.array([s for s in ts['angular_momentum_ejected']]) L1 = np.array([s[0] for s in ts['integrated_torque_on']]) L2 = np.array([s[1] for s in ts['integrated_torque_on']]) + K1 = np.array([s[0] for s in ts['angular_momentum_accreted_on']]) + K2 = np.array([s[1] for s in ts['angular_momentum_accreted_on']]) + E1 = np.array([s[0] for s in ts['work_done_on']]) E2 = np.array([s[1] for s in ts['work_done_on']]) @@ -184,28 +254,56 @@ def time_series(args): Mdot = Mdot1 + Mdot2 Ldot = Ldot1 + Ldot2 - - ax1.plot(t[:-1], Mdot, lw=1.0, c=c, label=fname) - ax2.plot(t[:-1], Ldot / Mdot, lw=1.0, c=c, label=fname) - - steady = np.where(t[:-1] > 12.0) - ax1.axhline(np.mean(Mdot[steady]), lw=1.0, c=c, ls='--') - ax2.axhline(np.mean(Ldot[steady]) / np.mean(Mdot[steady]), lw=1.0, c=c, ls='--') - + steady = np.where(t[:-1] > args.saturation_time) + + ax1.plot(t, M1, c='g', lw=1, ls='-', label=r'$M_1$') + ax1.plot(t, M2, c='r', lw=2, ls='--', label=r'$M_2$') + ax1.plot(t, Md, c='g', label=r'$M_{\rm disk}$') + ax1.plot(t, Me, c='b', label=r'$\Delta M_{\rm buffer}$') + ax1.plot(t, M1 + M2 + Md + Me, c='orange', lw=3, label=r'$M_{\rm tot}$') + + ax2.plot(t, L1, c='g', lw=2, ls='-', label=r'$L_{\rm grav, 1}$') + ax2.plot(t, L2, c='r', lw=2, ls='-', label=r'$L_{\rm grav, 2}$') + ax2.plot(t, K1, c='g', lw=1, ls='--', label=r'$L_{\rm acc, 1}$') + ax2.plot(t, K2, c='r', lw=1, ls='--', label=r'$L_{\rm acc, 2}$') + ax2.plot(t, Ld, c='g', label=r'$L_{\rm disk}$') + ax2.plot(t, Le, c='b', label=r'$\Delta L_{\rm buffer}$') + ax2.plot(t, L1 + L2 + K1 + K2 + Ld + Le, c='orange', lw=3, label=r'$L_{\rm tot}$') + + plot_moving_average(ax3, t[:-1], Mdot / Md[:-1], window_size=args.window_size, avg_only=args.avg_only, c=c, lw=2, label=fname) + plot_moving_average(ax4, t[:-1], Ldot / Mdot, window_size=args.window_size, avg_only=args.avg_only, c=c, lw=2, label=fname) + + # ax2.axhline(np.mean((Mdot / Md[:-1])[steady]), lw=1.0, c=c, ls='--') + # ax3.axhline(np.mean((Ldot / Mdot) [steady]), lw=1.0, c=c, ls='--') + ax3.axhline(np.mean(Mdot[steady]) / np.mean(Md[:-1][steady]), lw=1.0, c=c, ls='--') + ax4.axhline(np.mean(Ldot[steady]) / np.mean(Mdot [steady]), lw=1.0, c=c, ls='--') + + try: + ax3.axvline(t[steady][0], c='k', ls='--', lw=0.5) + ax4.axvline(t[steady][0], c='k', ls='--', lw=0.5) + except: + print("Warning: no data points are available after the saturation time (try with e.g. --saturation-time=50)") + + # ax1.set_yscale('log') ax1.legend() - ax1.set_ylabel(r'$\dot M$') - ax1.set_yscale('log') - ax2.set_xlabel("Orbits") - ax2.set_ylabel(r'$\dot L / \dot M$') + ax2.legend() + ax3.set_ylabel(r'$\dot M / M_{\rm disk}$') + ax3.set_yscale('log') + ax4.set_xlabel("Orbits") + ax4.set_ylabel(r'$\dot L / \dot M$') plt.show() + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("filenames", nargs='+') parser.add_argument("--movie", action='store_true') parser.add_argument("--time-series", '-t', action='store_true') + parser.add_argument("--avg-only", action='store_true') + parser.add_argument("--saturation-time", type=float, default=150.0) + parser.add_argument("--window-size", type=int, default=1000) parser.add_argument("--with-vel", action='store_true') parser.add_argument("--output", "-o", default="output.mp4") parser.add_argument("--sigma", default="default", type=str) diff --git a/tools/plot_binary_v1.py b/tools/plot_binary_v1.py deleted file mode 100755 index b5d0da5..0000000 --- a/tools/plot_binary_v1.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 - - - -import argparse -import numpy as np -import h5py -import matplotlib.pyplot as plt - - - -def get_ranges(args): - return dict( - sigma_range=eval(args.sigma, dict(auto=[None, None], fiducial=[ -6.0, 1.0])), - vr_range =eval(args.vr, dict(auto=[None, None], fiducial=[ -0.5, 0.5])), - vp_range =eval(args.vp, dict(auto=[None, None], fiducial=[ 0.0, 2.0]))) - - - -def plot_single_file( - fig, - filename, - sigma_range=[None, None], - vr_range=[None, None], - vp_range=[None, None]): - - axes, cb_axes = fig.subplots(nrows=2, ncols=3, gridspec_kw={'height_ratios': [19, 1]}) - h5f = h5py.File(filename, 'r') - - vx = h5f['x_vertices'][...] - vy = h5f['y_vertices'][...] - sigma = h5f['sigma'][...] - vr = h5f['radial_velocity'][...] - vp = h5f['phi_velocity'][...] - - X, Y = np.meshgrid(vx, vy) - - m0 = axes[0].pcolormesh(Y, X, np.log10(sigma.T), vmin=sigma_range[0], vmax=sigma_range[1], cmap='inferno') - m1 = axes[1].pcolormesh(Y, X, vr.T, vmin=vr_range[0], vmax=vr_range[1], cmap='viridis') - m2 = axes[2].pcolormesh(Y, X, vp.T, vmin=vp_range[0], vmax=vp_range[1], cmap='plasma') - - axes[0].set_title(r'$\log_{10} \Sigma$') - axes[1].set_title(r'$v_r$') - axes[2].set_title(r'$v_\phi$') - - for m, cax in zip([m0, m1, m2], cb_axes): - fig.colorbar(m, cax=cax, orientation='horizontal') - - for ax in axes: - ax.set_aspect('equal') - ax.set_xticks([]) - if ax is not axes[0]: - ax.set_yticks([]) - - axes[0].set_xlabel(r'$x$') - axes[0].set_ylabel(r'$y$') - - fig.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05, wspace=0.1, hspace=0.0) - fig.suptitle(filename) - - - -def make_movie(args): - from matplotlib.animation import FFMpegWriter - - dpi = 200 - res = 768 - - writer = FFMpegWriter(fps=10) - fig = plt.figure(figsize=[15, 8]) - - with writer.saving(fig, args.output, dpi): - for filename in args.filenames: - print(filename) - plot_single_file(fig, filename, **get_ranges(args)) - writer.grab_frame() - fig.clf() - - - -def raise_figure_windows(args): - for filename in args.filenames: - print(filename) - fig = plt.figure(figsize=[16, 6]) - plot_single_file(fig, filename, **get_ranges(args)) - plt.show() - - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("filenames", nargs='+') - parser.add_argument("--movie", action='store_true') - parser.add_argument("--output", "-o", default="output.mp4") - parser.add_argument("--sigma", default="fiducial", type=str) - parser.add_argument("--vr", default="fiducial", type=str) - parser.add_argument("--vp", default="fiducial", type=str) - - args = parser.parse_args() - - if args.movie: - make_movie(args) - else: - raise_figure_windows(args)