From 06bc1a2efef78f73b0bcb43e54869ff5a1bab7c1 Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Thu, 23 Sep 2021 13:45:09 -0700 Subject: [PATCH] Sharded fields dump (#1738) * Add support for fully local HDF5 files and shared dumping of meep::structure * Add support for fully local HDF5 files and shared dumping of meep::structure * Update python func docs * Update python API documentation * Dump/Load of 'fields' The PR is incomplete (does not include the dft fields) and also has a lot of debugging statements. * Save dft chunks * Remove debug log stmts * Add saving of time value and also reorg tests. * Fix dft-chunk saving for the single-parallel-file mode. * Abort when trying to dump fields with non-null polarization state * Clean up test_dump_load.py and add test_dump_fails_for_non_null_polarization_state test * Add new tests/dump_load to set of files to ignore * Clean up the test to remove unnecessary stuff from the copied over test * Also dump/load 'f_w_prev' * Fix typo causing build breakage * load fields at the very end of init_sim after add_sources * remove init_sim before loading fields since that now works. --- .gitignore | 1 + python/Makefile.am | 11 +- python/simulation.py | 66 ++++++-- python/tests/test_dump_load.py | 199 ++++++++++++++++++++++ python/tests/test_simulation.py | 71 -------- src/Makefile.am | 2 +- src/dft.cpp | 35 ++-- src/fields.cpp | 9 + src/fields_dump.cpp | 286 ++++++++++++++++++++++++++++++++ src/meep.hpp | 26 ++- tests/Makefile.am | 15 +- tests/dump_load.cpp | 232 ++++++++++++++++++++++++++ 12 files changed, 844 insertions(+), 109 deletions(-) create mode 100644 python/tests/test_dump_load.py create mode 100644 src/fields_dump.cpp create mode 100644 tests/dump_load.cpp diff --git a/.gitignore b/.gitignore index 1a667d31c..4ed1ef7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ tests/convergence_cyl_waveguide tests/cyl-ellipsoid-ll tests/cylindrical tests/dft-fields +tests/dump_load tests/flux tests/gdsII-3d tests/h5test diff --git a/python/Makefile.am b/python/Makefile.am index 6d8a63795..b34526be4 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -64,14 +64,15 @@ TESTS = \ $(TEST_DIR)/test_get_point.py \ $(TEST_DIR)/test_holey_wvg_bands.py \ $(TEST_DIR)/test_holey_wvg_cavity.py \ - $(KDOM_TEST) \ + $(KDOM_TEST) \ $(TEST_DIR)/test_ldos.py \ - $(MDPYTEST) \ + $(TEST_DIR)/test_dump_load.py \ + $(MDPYTEST) \ $(TEST_DIR)/test_material_grid.py \ $(TEST_DIR)/test_medium_evaluations.py \ - $(MPBPYTEST) \ - $(MODE_COEFFS_TEST) \ - $(MODE_DECOMPOSITION_TEST) \ + $(MPBPYTEST) \ + $(MODE_COEFFS_TEST) \ + $(MODE_DECOMPOSITION_TEST) \ $(TEST_DIR)/test_multilevel_atom.py \ $(TEST_DIR)/test_n2f_periodic.py \ $(TEST_DIR)/test_oblique_source.py \ diff --git a/python/simulation.py b/python/simulation.py index 568995705..5485f6a94 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -1250,6 +1250,7 @@ def __init__(self, self.load_single_parallel_file = True self.load_structure_file = None + self.load_fields_file = None self.special_kz = False if self.cell_size.z == 0 and self.k_point and self.k_point.z != 0: @@ -1874,29 +1875,48 @@ def dump_structure(self, fname, single_parallel_file=True): Dumps the structure to the file `fname`. """ if self.structure is None: - raise ValueError("Fields must be initialized before calling dump_structure") + raise ValueError("Structure must be initialized before calling dump_structure") self.structure.dump(fname, single_parallel_file) + print("Dumped structure to file: %s (%s)" % (fname, str(single_parallel_file))) def load_structure(self, fname, single_parallel_file=True): """ - Loads a structure from the file `fname`. A file name to load can also be passed to - the `Simulation` constructor via the `load_structure` keyword argument. + Loads a structure from the file `fname`. """ if self.structure is None: - raise ValueError("Fields must be initialized before calling load_structure") + raise ValueError("Structure must be initialized before loading structure from file '%s'" % fname) self.structure.load(fname, single_parallel_file) + print("Loaded structure from file: %s (%s)" % (fname, str(single_parallel_file))) + + def dump_fields(self, fname, single_parallel_file=True): + """ + Dumps the fields to the file `fname`. + """ + if self.fields is None: + raise ValueError("Fields must be initialized before calling dump_fields") + self.fields.dump(fname, single_parallel_file) + print("Dumped fields to file: %s (%s)" % (fname, str(single_parallel_file))) + + def load_fields(self, fname, single_parallel_file=True): + """ + Loads a fields from the file `fname`. + """ + if self.fields is None: + raise ValueError("Fields must be initialized before loading fields from file '%s'" % fname) + self.fields.load(fname, single_parallel_file) + print("Loaded fields from file: %s (%s)" % (fname, str(single_parallel_file))) def dump_chunk_layout(self, fname): """ Dumps the chunk layout to file `fname`. """ if self.structure is None: - raise ValueError("Fields must be initialized before calling load_structure") + raise ValueError("Structure must be initialized before calling dump_chunk_layout") self.structure.dump_chunk_layout(fname) def load_chunk_layout(self, br, source): if self.structure is None: - raise ValueError("Fields must be initialized before calling load_structure") + raise ValueError("Structure must be initialized before loading chunk layout from file '%s'" % fname) if isinstance(source, Simulation): vols = source.structure.get_chunk_volumes() @@ -1918,18 +1938,22 @@ def _get_load_dump_dirname(self, dirname, single_parallel_file): dump_dirname = os.path.join(dirname, 'rank%02d' % mp.my_rank()) return dump_dirname - def dump(self, dirname, structure=True, single_parallel_file=True): + def dump(self, dirname, dump_structure=True, dump_fields=True, single_parallel_file=True): """ Dumps simulation state. """ dump_dirname = self._get_load_dump_dirname(dirname, single_parallel_file) os.makedirs(dump_dirname, exist_ok=True) - if structure: + if dump_structure: structure_dump_filename = os.path.join(dump_dirname, 'structure.h5') self.dump_structure(structure_dump_filename, single_parallel_file) - def load(self, dirname, structure=True, single_parallel_file=True): + if dump_fields: + fields_dump_filename = os.path.join(dump_dirname, 'fields.h5') + self.dump_fields(fields_dump_filename, single_parallel_file) + + def load(self, dirname, load_structure=True, load_fields=True, single_parallel_file=True): """ Loads simulation state. @@ -1938,8 +1962,22 @@ def load(self, dirname, structure=True, single_parallel_file=True): """ dump_dirname = self._get_load_dump_dirname(dirname, single_parallel_file) self.load_single_parallel_file = single_parallel_file - if structure: - self.load_structure_file = os.path.join(dump_dirname, 'structure.h5') + + if load_structure: + load_structure_file = os.path.join(dump_dirname, 'structure.h5') + # If structure is already initialized, load it straight away. + # Otherwise, do a delayed load. + if self.structure: + self.load_structure(load_structure_file, self.load_single_parallel_file) + else: + self.load_structure_file = load_structure_file + + if load_fields: + load_fields_file = os.path.join(dump_dirname, 'fields.h5') + if self.fields: + self.load_fields(load_fields_file, self.load_single_parallel_file) + else: + self.load_fields_file = load_fields_file def init_sim(self): if self._is_initialized: @@ -1988,7 +2026,11 @@ def init_sim(self): hook() self._is_initialized = True - + + if self.load_fields_file: + self.load_fields( + self.load_fields_file, self.load_single_parallel_file) + def using_real_fields(self): cond1 = self.is_cylindrical and self.m != 0 cond2 = any([s.phase.imag for s in self.symmetries]) diff --git a/python/tests/test_dump_load.py b/python/tests/test_dump_load.py new file mode 100644 index 000000000..95c843f72 --- /dev/null +++ b/python/tests/test_dump_load.py @@ -0,0 +1,199 @@ +import itertools +import os +import re +import sys +import unittest +import warnings +import h5py +import numpy as np +import meep as mp + +try: + unicode +except NameError: + unicode = str + + +class TestLoadDump(unittest.TestCase): + + fname_base = re.sub(r'\.py$', '', os.path.split(sys.argv[0])[1]) + fname = fname_base + '-ez-000200.00.h5' + + def setUp(self): + print("Running {}".format(self._testMethodName)) + + @classmethod + def setUpClass(cls): + cls.temp_dir = mp.make_output_directory() + print("Saving temp files to dir: {}".format(cls.temp_dir)) + + @classmethod + def tearDownClass(cls): + mp.delete_directory(cls.temp_dir) + + # Tests various combinations of dumping/loading structure & chunk layout. + def _load_dump_structure(self, chunk_file=False, chunk_sim=False, single_parallel_file=True): + from meep.materials import Al + resolution = 50 + cell = mp.Vector3(5, 5) + sources = mp.Source(src=mp.GaussianSource(1, fwidth=0.2), center=mp.Vector3(), component=mp.Ez) + one_by_one = mp.Vector3(1, 1, mp.inf) + geometry = [mp.Block(material=Al, center=mp.Vector3(), size=one_by_one), + mp.Block(material=mp.Medium(epsilon=13), center=mp.Vector3(1), size=one_by_one)] + pml_layers = [mp.PML(0.5)] + + symmetries = [mp.Mirror(mp.Y)] + + sim1 = mp.Simulation(resolution=resolution, + cell_size=cell, + boundary_layers=pml_layers, + geometry=geometry, + symmetries=symmetries, + sources=[sources]) + + sample_point = mp.Vector3(0.12, -0.29) + ref_field_points = [] + + def get_ref_field_point(sim): + p = sim.get_field_point(mp.Ez, sample_point) + ref_field_points.append(p.real) + + sim1.run(mp.at_every(5, get_ref_field_point), until=50) + + dump_dirname = os.path.join(self.temp_dir, 'test_load_dump_structure') + sim1.dump(dump_dirname, dump_structure=True, dump_fields=False, single_parallel_file=single_parallel_file) + + dump_chunk_fname = None + chunk_layout = None + if chunk_file: + dump_chunk_fname = os.path.join(dump_dirname, 'chunk_layout.h5') + sim1.dump_chunk_layout(dump_chunk_fname) + chunk_layout = dump_chunk_fname + if chunk_sim: + chunk_layout = sim1 + + sim = mp.Simulation(resolution=resolution, + cell_size=cell, + boundary_layers=pml_layers, + sources=[sources], + symmetries=symmetries, + chunk_layout=chunk_layout) + sim.load(dump_dirname, load_structure=True, load_fields=False, single_parallel_file=single_parallel_file) + field_points = [] + + def get_field_point(sim): + p = sim.get_field_point(mp.Ez, sample_point) + field_points.append(p.real) + + sim.run(mp.at_every(5, get_field_point), until=50) + + for ref_pt, pt in zip(ref_field_points, field_points): + self.assertAlmostEqual(ref_pt, pt) + + def test_load_dump_structure(self): + self._load_dump_structure() + + @unittest.skipIf(not mp.with_mpi(), "MPI specific test") + def test_load_dump_structure_sharded(self): + self._load_dump_structure(single_parallel_file=False) + + def test_load_dump_chunk_layout_file(self): + self._load_dump_structure(chunk_file=True) + + def test_load_dump_chunk_layout_sim(self): + self._load_dump_structure(chunk_sim=True) + + # Tests dumping/loading of fields & structure. + def _load_dump_fields(self, single_parallel_file=True): + resolution = 50 + cell = mp.Vector3(5, 5) + sources = mp.Source(src=mp.GaussianSource(1, fwidth=0.4), center=mp.Vector3(), component=mp.Ez) + one_by_one = mp.Vector3(1, 1, mp.inf) + geometry = [mp.Block(material=mp.Medium(index=3.2), center=mp.Vector3(), size=one_by_one), + mp.Block(material=mp.Medium(epsilon=13), center=mp.Vector3(1), size=one_by_one)] + pml_layers = [mp.PML(0.5)] + symmetries = [mp.Mirror(mp.Y)] + + sim1 = mp.Simulation(resolution=resolution, + cell_size=cell, + boundary_layers=pml_layers, + geometry=geometry, + symmetries=symmetries, + sources=[sources]) + + sample_point = mp.Vector3(0.12, -0.29) + + dump_dirname = os.path.join(self.temp_dir, 'test_load_dump_fields') + os.makedirs(dump_dirname, exist_ok=True) + + ref_field_points = {} + def get_ref_field_point(sim): + p = sim.get_field_point(mp.Ez, sample_point) + ref_field_points[sim.meep_time()] = p.real + + # First run until t=15 and save structure/fields + sim1.run(mp.at_every(1, get_ref_field_point), until=15) + sim1.dump(dump_dirname, dump_structure=True, dump_fields=True, single_parallel_file=single_parallel_file) + + # Then continue running another 5 until t=20 + sim1.run(mp.at_every(1, get_ref_field_point), until=5) + + # Now create a new simulation and try restoring state. + sim = mp.Simulation(resolution=resolution, + cell_size=cell, + boundary_layers=pml_layers, + sources=[sources], + symmetries=symmetries, + chunk_layout=sim1) + # Just restore structure first. + sim.load(dump_dirname, load_structure=True, load_fields=False, single_parallel_file=single_parallel_file) + field_points = {} + + def get_field_point(sim): + p = sim.get_field_point(mp.Ez, sample_point) + field_points[sim.meep_time()] = p.real + + # Now load the fields (at t=15) and then continue to t=20 + sim.load(dump_dirname, load_structure=False, load_fields=True, single_parallel_file=single_parallel_file) + sim.run(mp.at_every(1, get_field_point), until=5) + + for t, v in field_points.items(): + self.assertAlmostEqual(ref_field_points[t], v) + + def test_load_dump_fields(self): + self._load_dump_fields() + + @unittest.skipIf(not mp.with_mpi(), "MPI specific test") + def test_load_dump_fields_sharded(self): + self._load_dump_fields(single_parallel_file=False) + + # This assertRaisesRegex check does not play well with MPI due to the + # underlying call to meep::abort + @unittest.skipIf(mp.with_mpi(), "MPI specific test") + def test_dump_fails_for_non_null_polarization_state(self): + resolution = 50 + cell = mp.Vector3(5, 5) + sources = mp.Source(src=mp.GaussianSource(1, fwidth=0.4), center=mp.Vector3(), component=mp.Ez) + one_by_one = mp.Vector3(1, 1, mp.inf) + from meep.materials import Al + geometry = [mp.Block(material=Al, center=mp.Vector3(), size=one_by_one), + mp.Block(material=mp.Medium(epsilon=13), center=mp.Vector3(1), size=one_by_one)] + + sim = mp.Simulation(resolution=resolution, + cell_size=cell, + boundary_layers=[], + geometry=geometry, + symmetries=[], + sources=[sources]) + + dump_dirname = os.path.join(self.temp_dir, 'test_load_dump_fields') + os.makedirs(dump_dirname, exist_ok=True) + + sim.run(until=1) + # NOTE: We do not yet support checkpoint/restore when there is a + # non-null polarization_state + with self.assertRaisesRegex(RuntimeError, 'meep: non-null polarization_state in fields::dump'): + sim.dump(dump_dirname, dump_structure=True, dump_fields=True) + +if __name__ == '__main__': + unittest.main() diff --git a/python/tests/test_simulation.py b/python/tests/test_simulation.py index f85afeb7f..24d183467 100644 --- a/python/tests/test_simulation.py +++ b/python/tests/test_simulation.py @@ -302,77 +302,6 @@ def test_in_box_volumes(self): sim.field_energy_in_box(tv) sim.field_energy_in_box(v) - def _load_dump_structure(self, chunk_file=False, chunk_sim=False, single_parallel_file=True): - from meep.materials import Al - resolution = 50 - cell = mp.Vector3(5, 5) - sources = mp.Source(src=mp.GaussianSource(1, fwidth=0.2), center=mp.Vector3(), component=mp.Ez) - one_by_one = mp.Vector3(1, 1, mp.inf) - geometry = [mp.Block(material=Al, center=mp.Vector3(), size=one_by_one), - mp.Block(material=mp.Medium(epsilon=13), center=mp.Vector3(1), size=one_by_one)] - pml_layers = [mp.PML(0.5)] - - symmetries = [mp.Mirror(mp.Y)] - - sim1 = mp.Simulation(resolution=resolution, - cell_size=cell, - boundary_layers=pml_layers, - geometry=geometry, - symmetries=symmetries, - sources=[sources]) - - sample_point = mp.Vector3(0.12, -0.29) - ref_field_points = [] - - def get_ref_field_point(sim): - p = sim.get_field_point(mp.Ez, sample_point) - ref_field_points.append(p.real) - - sim1.run(mp.at_every(5, get_ref_field_point), until=50) - - dump_dirname = os.path.join(self.temp_dir, 'test_load_dump') - sim1.dump(dump_dirname, structure=True, single_parallel_file=single_parallel_file) - - dump_chunk_fname = None - chunk_layout = None - if chunk_file: - dump_chunk_fname = os.path.join(dump_dirname, 'chunk_layout.h5') - sim1.dump_chunk_layout(dump_chunk_fname) - chunk_layout = dump_chunk_fname - if chunk_sim: - chunk_layout = sim1 - - sim = mp.Simulation(resolution=resolution, - cell_size=cell, - boundary_layers=pml_layers, - sources=[sources], - symmetries=symmetries, - chunk_layout=chunk_layout) - sim.load(dump_dirname, structure=True, single_parallel_file=single_parallel_file) - field_points = [] - - def get_field_point(sim): - p = sim.get_field_point(mp.Ez, sample_point) - field_points.append(p.real) - - sim.run(mp.at_every(5, get_field_point), until=50) - - for ref_pt, pt in zip(ref_field_points, field_points): - self.assertAlmostEqual(ref_pt, pt) - - def test_load_dump_structure(self): - self._load_dump_structure() - - @unittest.skipIf(not mp.with_mpi(), "MPI specific test") - def test_load_dump_structure_sharded(self): - self._load_dump_structure(single_parallel_file=False) - - def test_load_dump_chunk_layout_file(self): - self._load_dump_structure(chunk_file=True) - - def test_load_dump_chunk_layout_sim(self): - self._load_dump_structure(chunk_sim=True) - def test_get_array_output(self): sim = self.init_simple_simulation() sim.use_output_directory(self.temp_dir) diff --git a/src/Makefile.am b/src/Makefile.am index 469f8aec2..bf2111a57 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,7 +12,7 @@ bicgstab.hpp meepgeom.hpp material_data.hpp adjust_verbosity.hpp libmeep_la_SOURCES = array_slice.cpp anisotropic_averaging.cpp \ bands.cpp boundaries.cpp bicgstab.cpp casimir.cpp \ cw_fields.cpp dft.cpp dft_ldos.cpp energy_and_flux.cpp \ -fields.cpp loop_in_chunks.cpp h5fields.cpp h5file.cpp \ +fields.cpp fields_dump.cpp loop_in_chunks.cpp h5fields.cpp h5file.cpp \ initialize.cpp integrate.cpp integrate2.cpp material_data.cpp monitor.cpp mympi.cpp \ multilevel-atom.cpp near2far.cpp output_directory.cpp random.cpp \ sources.cpp step.cpp step_db.cpp stress.cpp structure.cpp structure_dump.cpp \ diff --git a/src/dft.cpp b/src/dft.cpp index ae8e4f4e4..1f15ab3d3 100644 --- a/src/dft.cpp +++ b/src/dft.cpp @@ -377,18 +377,33 @@ void dft_chunk::operator-=(const dft_chunk &chunk) { } } -size_t dft_chunks_Ntotal(dft_chunk *dft_chunks, size_t *my_start) { - size_t n = 0; +size_t my_dft_chunks_Ntotal(dft_chunk *dft_chunks, size_t *my_start) { + // When writing to a sharded file, we write out only the chunks we own. +size_t n = 0; for (dft_chunk *cur = dft_chunks; cur; cur = cur->next_in_dft) n += cur->N * cur->omega.size() * 2; + + *my_start = 0; + return n; +} + +size_t dft_chunks_Ntotal(dft_chunk *dft_chunks, size_t *my_start) { + // If writing to a single parallel file, we are compute our chunks offset + // into the single-parallel-file that has all the data. + size_t n = my_dft_chunks_Ntotal(dft_chunks, my_start); *my_start = partial_sum_to_all(n) - n; // sum(n) for processes before this return sum_to_all(n); } +size_t dft_chunks_Ntotal(dft_chunk *dft_chunks, size_t *my_start, bool single_parallel_file) { + return single_parallel_file ? dft_chunks_Ntotal(dft_chunks, my_start) : + my_dft_chunks_Ntotal(dft_chunks, my_start); +} + // Note: the file must have been created in parallel mode, typically via fields::open_h5file. -void save_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const char *dprefix) { +void save_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const char *dprefix, bool single_parallel_file) { size_t istart; - size_t n = dft_chunks_Ntotal(dft_chunks, &istart); + size_t n = dft_chunks_Ntotal(dft_chunks, &istart, single_parallel_file); char dataname[1024]; snprintf(dataname, 1024, @@ -405,13 +420,13 @@ void save_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const file->done_writing_chunks(); } -void save_dft_hdf5(dft_chunk *dft_chunks, component c, h5file *file, const char *dprefix) { - save_dft_hdf5(dft_chunks, component_name(c), file, dprefix); +void save_dft_hdf5(dft_chunk *dft_chunks, component c, h5file *file, const char *dprefix, bool single_parallel_file) { + save_dft_hdf5(dft_chunks, component_name(c), file, dprefix, single_parallel_file); } -void load_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const char *dprefix) { +void load_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const char *dprefix, bool single_parallel_file) { size_t istart; - size_t n = dft_chunks_Ntotal(dft_chunks, &istart); + size_t n = dft_chunks_Ntotal(dft_chunks, &istart, single_parallel_file); char dataname[1024]; snprintf(dataname, 1024, @@ -432,8 +447,8 @@ void load_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const } } -void load_dft_hdf5(dft_chunk *dft_chunks, component c, h5file *file, const char *dprefix) { - load_dft_hdf5(dft_chunks, component_name(c), file, dprefix); +void load_dft_hdf5(dft_chunk *dft_chunks, component c, h5file *file, const char *dprefix, bool single_parallel_file) { + load_dft_hdf5(dft_chunks, component_name(c), file, dprefix, single_parallel_file); } dft_flux::dft_flux(const component cE_, const component cH_, dft_chunk *E_, dft_chunk *H_, diff --git a/src/fields.cpp b/src/fields.cpp index 6d1ba792a..d9dcda7df 100644 --- a/src/fields.cpp +++ b/src/fields.cpp @@ -722,6 +722,15 @@ void fields::unset_solve_cw_omega() { chunks[i]->unset_solve_cw_omega(); } +void fields::log(const char* prefix) { + master_printf("%sFields State:\n", prefix); + master_printf("%s a = %g, dt = %g\n", prefix, a, dt); + master_printf("%s m = %g, beta = %g\n", prefix, m, beta); + master_printf("%s t = %d, phasein_time = %d, is_real = %d\n", prefix, t, phasein_time, is_real); + master_printf("\n"); + master_printf("%s num_chunks = %d (shared=%d)\n", prefix, num_chunks, shared_chunks); +} + /* implement mirror boundary conditions for i outside 0..n-1: */ int mirrorindex(int i, int n) { return i >= n ? 2 * n - 1 - i : (i < 0 ? -1 - i : i); } diff --git a/src/fields_dump.cpp b/src/fields_dump.cpp new file mode 100644 index 000000000..567a989e8 --- /dev/null +++ b/src/fields_dump.cpp @@ -0,0 +1,286 @@ +/* Copyright (C) 2005-2021 Massachusetts Institute of Technology +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2, or (at your option) +% any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software Foundation, +% Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +// Dump/load raw fields data to/from an HDF5 file. Only +// works if the number of processors/chunks is the same. + +#include +#include +#include +#include + +#include + +#include "meep.hpp" +#include "meep_internals.hpp" + +namespace meep { + +void fields::dump_fields_chunk_field(h5file *h5f, bool single_parallel_file, + const std::string &field_name, + FieldPtrGetter field_ptr_getter) { + /* + * make/save a num_chunks x NUM_FIELD_COMPONENTS x 2 array counting + * the number of entries in the 'field_name' array for each chunk. + * + * When 'single_parallel_file' is true, we are creating a single block of data + * for ALL chunks (that are merged using MPI). Otherwise, we are just + * making a copy of just the chunks that are ours. + */ + int my_num_chunks = 0; + for (int i = 0; i < num_chunks; i++) { + my_num_chunks += (single_parallel_file || chunks[i]->is_mine()); + } + size_t num_f_size = my_num_chunks * NUM_FIELD_COMPONENTS * 2; + std::vector num_f_(num_f_size); + size_t my_ntot = 0; + for (int i = 0, chunk_i = 0; i < num_chunks; i++) { + if (chunks[i]->is_mine()) { + FOR_FIELD_TYPES(ft) { + if (chunks[i]->pol[ft] != NULL) + meep::abort("non-null polarization_state in fields::dump (unsupported)"); + } + size_t ntot = chunks[i]->gv.ntot(); + for (int c = 0; c < NUM_FIELD_COMPONENTS; ++c) { + for (int d = 0; d < 2; ++d) { + realnum **f = field_ptr_getter(chunks[i], c, d); + if (*f) { + my_ntot += (num_f_[(chunk_i * NUM_FIELD_COMPONENTS + c) * 2 + d] = ntot); + } + } + } + } + chunk_i += (chunks[i]->is_mine() || single_parallel_file); + } + + std::vector num_f; + if (single_parallel_file) { + num_f.resize(num_f_size); + sum_to_master(num_f_.data(), num_f.data(), num_f_size); + } else { + num_f = std::move(num_f_); + } + + /* determine total dataset size and offset of this process's data */ + size_t my_start = 0; + size_t ntotal = my_ntot; + if (single_parallel_file) { + my_start = partial_sum_to_all(my_ntot) - my_ntot; + ntotal = sum_to_all(my_ntot); + } + + size_t dims[3] = {(size_t)my_num_chunks, NUM_FIELD_COMPONENTS, 2}; + size_t start[3] = {0, 0, 0}; + std::string num_f_name = std::string("num_") + field_name; + h5f->create_data(num_f_name.c_str(), 3, dims); + if (am_master() || !single_parallel_file) { + h5f->write_chunk(3, start, dims, num_f.data()); + } + + /* write the data */ + h5f->create_data(field_name.c_str(), 1, &ntotal, false /* append_data */, false /* single_precision */); + for (int i = 0; i < num_chunks; i++) { + if (chunks[i]->is_mine()) { + size_t ntot = chunks[i]->gv.ntot(); + for (int c = 0; c < NUM_FIELD_COMPONENTS; ++c) { + for (int d = 0; d < 2; ++d) { + realnum **f = field_ptr_getter(chunks[i], c, d); + if (*f) { + h5f->write_chunk(1, &my_start, &ntot, *f); + my_start += ntot; + } + } + } + } + } +} + +void fields::dump(const char *filename, bool single_parallel_file) { + if (verbosity > 0) { + printf("creating fields output file \"%s\" (%d)...\n", filename, single_parallel_file); + log(); + } + + h5file file(filename, h5file::WRITE, single_parallel_file, !single_parallel_file); + + // Write out the current time 't' + size_t dims[1] = {1}; + size_t start[1] = {0}; + size_t _t[1] = {(size_t)t}; + file.create_data("t", 1, dims); + if (am_master() || !single_parallel_file) file.write_chunk(1, start, dims, _t); + + dump_fields_chunk_field( + &file, single_parallel_file, "f", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f[c][d]); }); + dump_fields_chunk_field( + &file, single_parallel_file, "f_u", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f_u[c][d]); }); + dump_fields_chunk_field( + &file, single_parallel_file, "f_w", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f_w[c][d]); }); + dump_fields_chunk_field( + &file, single_parallel_file, "f_cond", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f_cond[c][d]); }); + dump_fields_chunk_field( + &file, single_parallel_file, "f_w_prev", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f_w_prev[c][d]); }); + + // Dump DFT chunks. + for (int i = 0; i < num_chunks; i++) { + if (single_parallel_file || chunks[i]->is_mine()) { + char dataname[1024]; + snprintf(dataname, 1024, "chunk%02d", i); + save_dft_hdf5(chunks[i]->dft_chunks, dataname, &file, 0, single_parallel_file); + } + } +} + +void fields::load_fields_chunk_field(h5file *h5f, bool single_parallel_file, + const std::string &field_name, + FieldPtrGetter field_ptr_getter) { + int my_num_chunks = 0; + for (int i = 0; i < num_chunks; i++) { + my_num_chunks += (single_parallel_file || chunks[i]->is_mine()); + } + size_t num_f_size = my_num_chunks * NUM_FIELD_COMPONENTS * 2; + std::vector num_f(num_f_size); + + int rank; + size_t dims[3], _dims[3] = {(size_t)my_num_chunks, NUM_FIELD_COMPONENTS, 2}; + size_t start[3] = {0, 0, 0}; + + std::string num_f_name = std::string("num_") + field_name; + h5f->read_size(num_f_name.c_str(), &rank, dims, 3); + if (rank != 3 || _dims[0] != dims[0] || _dims[1] != dims[1] || _dims[2] != dims[2]) + meep::abort("chunk mismatch in fields::load"); + if (am_master() || !single_parallel_file) h5f->read_chunk(3, start, dims, num_f.data()); + + if (single_parallel_file) { + h5f->prevent_deadlock(); + broadcast(0, num_f.data(), dims[0] * dims[1] * dims[2]); + } + + /* allocate data as needed and check sizes */ + size_t my_ntot = 0; + for (int i = 0, chunk_i = 0; i < num_chunks; i++) { + if (chunks[i]->is_mine()) { + size_t ntot = chunks[i]->gv.ntot(); + for (int c = 0; c < NUM_FIELD_COMPONENTS; ++c) { + for (int d = 0; d < 2; ++d) { + size_t n = num_f[(chunk_i * NUM_FIELD_COMPONENTS + c) * 2 + d]; + realnum **f = field_ptr_getter(chunks[i], c, d); + if (n == 0) { + delete[] * f; + *f = NULL; + } else { + if (n != ntot) + meep::abort("grid size mismatch %zd vs %zd in fields::load", n, ntot); + *f = new realnum[ntot]; + my_ntot += ntot; + } + } + } + } + chunk_i += (chunks[i]->is_mine() || single_parallel_file); + } + + /* determine total dataset size and offset of this process's data */ + size_t my_start = 0; + size_t ntotal = my_ntot; + if (single_parallel_file) { + my_start = partial_sum_to_all(my_ntot) - my_ntot; + ntotal = sum_to_all(my_ntot); + } + + /* read the data */ + h5f->read_size(field_name.c_str(), &rank, dims, 1); + if (rank != 1 || dims[0] != ntotal) { + meep::abort( + "inconsistent data size for '%s' in fields::load (rank, dims[0]): " + "(%d, %zu) != (1, %zu)", + field_name.c_str(), rank, dims[0], ntotal); + } + for (int i = 0; i < num_chunks; i++) { + if (chunks[i]->is_mine()) { + size_t ntot = chunks[i]->gv.ntot(); + for (int c = 0; c < NUM_FIELD_COMPONENTS; ++c) { + for (int d = 0; d < 2; ++d) { + realnum **f = field_ptr_getter(chunks[i], c, d); + if (*f) { + h5f->read_chunk(1, &my_start, &ntot, *f); + my_start += ntot; + } + } + } + } + } +} + +void fields::load(const char *filename, bool single_parallel_file) { + if (verbosity > 0) + printf("reading fields from file \"%s\" (%d)...\n", filename, single_parallel_file); + + h5file file(filename, h5file::READONLY, single_parallel_file, !single_parallel_file); + + // Read in the current time 't' + int rank; + size_t dims[1] = {1}; + size_t start[1] = {0}; + size_t _t[1]; + file.read_size("t", &rank, dims, 1); + if (rank != 1 || dims[0] != 1) meep::abort("time size mismatch in fields::load"); + if (am_master() || !single_parallel_file) file.read_chunk(1, start, dims, _t); + + if (single_parallel_file) { + file.prevent_deadlock(); + broadcast(0, _t, dims[0]); + } + + t = static_cast(_t[0]); + calc_sources(time()); + + load_fields_chunk_field( + &file, single_parallel_file, "f", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f[c][d]); }); + load_fields_chunk_field( + &file, single_parallel_file, "f_u", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f_u[c][d]); }); + load_fields_chunk_field( + &file, single_parallel_file, "f_w", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f_w[c][d]); }); + load_fields_chunk_field( + &file, single_parallel_file, "f_cond", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f_cond[c][d]); }); + load_fields_chunk_field( + &file, single_parallel_file, "f_w_prev", + [](fields_chunk *chunk, int c, int d) { return &(chunk->f_w_prev[c][d]); }); + + // Load DFT chunks. + for (int i = 0; i < num_chunks; i++) { + if (single_parallel_file || chunks[i]->is_mine()) { + char dataname[1024]; + snprintf(dataname, 1024, "chunk%02d", i); + load_dft_hdf5(chunks[i]->dft_chunks, dataname, &file, 0, single_parallel_file); + } + } + + if (verbosity > 0) + log(); +} + +} // namespace meep diff --git a/src/meep.hpp b/src/meep.hpp index 3caeb9366..9c1a95fd8 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -1199,10 +1199,10 @@ class dft_chunk { int decimation_factor; }; -void save_dft_hdf5(dft_chunk *dft_chunks, component c, h5file *file, const char *dprefix = 0); -void load_dft_hdf5(dft_chunk *dft_chunks, component c, h5file *file, const char *dprefix = 0); -void save_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const char *dprefix = 0); -void load_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const char *dprefix = 0); +void save_dft_hdf5(dft_chunk *dft_chunks, component c, h5file *file, const char *dprefix = 0, bool single_parallel_file=true); +void load_dft_hdf5(dft_chunk *dft_chunks, component c, h5file *file, const char *dprefix = 0, bool single_parallel_file=true); +void save_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const char *dprefix = 0, bool single_parallel_file=true); +void load_dft_hdf5(dft_chunk *dft_chunks, const char *name, h5file *file, const char *dprefix = 0, bool single_parallel_file=true); // dft.cpp (normally created with fields::add_dft_flux) class dft_flux { @@ -1730,6 +1730,7 @@ class fields { void remove_susceptibilities(); void remove_fluxes(); void reset(); + void log(const char* prefix = ""); // time.cpp std::vector time_spent_on(time_sink sink); @@ -1746,6 +1747,15 @@ class fields { volume total_volume(void) const; + // fields_dump.cpp + // Dump fields to specified file. If 'single_parallel_file' + // is 'true' (the default) - then all processes write to the same/single file + // file after computing their respective offsets into this file. When set to + // 'false', each process writes data for the chunks it owns to a separate + // (process unique) file. + void dump(const char *filename, bool single_parallel_file=true); + void load(const char *filename, bool single_parallel_file=true); + // h5fields.cpp: // low-level function: void output_hdf5(h5file *file, const char *dataname, int num_fields, const component *components, @@ -2223,6 +2233,14 @@ class fields { std::complex A(const vec &), std::complex amp, component c0, direction d, int has_tm, int has_te); + // fields_dump.cpp + // Helper methods for dumping field chunks. + using FieldPtrGetter = std::function; + void dump_fields_chunk_field(h5file *h5f, bool single_parallel_file, + const std::string& field_name, FieldPtrGetter field_ptr_getter); + void load_fields_chunk_field(h5file *h5f, bool single_parallel_file, + const std::string& field_name, FieldPtrGetter field_ptr_getter); + public: // monitor.cpp std::complex get_field(component c, const ivec &iloc, bool parallel = true) const; diff --git a/tests/Makefile.am b/tests/Makefile.am index 53eae02ae..1ce61b95b 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,8 +1,8 @@ SRC = aniso_disp.cpp bench.cpp bragg_transmission.cpp \ -convergence_cyl_waveguide.cpp cylindrical.cpp flux.cpp harmonics.cpp \ -integrate.cpp known_results.cpp near2far.cpp one_dimensional.cpp \ -physical.cpp stress_tensor.cpp symmetry.cpp three_d.cpp \ -two_dimensional.cpp 2D_convergence.cpp h5test.cpp pml.cpp +convergence_cyl_waveguide.cpp cylindrical.cpp dump_load.cpp flux.cpp \ +harmonics.cpp integrate.cpp known_results.cpp near2far.cpp \ +one_dimensional.cpp physical.cpp stress_tensor.cpp symmetry.cpp \ +three_d.cpp two_dimensional.cpp 2D_convergence.cpp h5test.cpp pml.cpp EXTRA_DIST = $(SRC) @@ -16,7 +16,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src -DDATADIR=\"$(srcdir)/\" .SUFFIXES = .dac .done -check_PROGRAMS = aniso_disp bench bragg_transmission convergence_cyl_waveguide cylindrical flux harmonics integrate known_results near2far one_dimensional physical stress_tensor symmetry three_d two_dimensional 2D_convergence h5test pml pw-source-ll ring-ll cyl-ellipsoid-ll absorber-1d-ll array-slice-ll user-defined-material dft-fields gdsII-3d bend-flux-ll array-metadata +check_PROGRAMS = aniso_disp bench bragg_transmission convergence_cyl_waveguide cylindrical dump_load flux harmonics integrate known_results near2far one_dimensional physical stress_tensor symmetry three_d two_dimensional 2D_convergence h5test pml pw-source-ll ring-ll cyl-ellipsoid-ll absorber-1d-ll array-slice-ll user-defined-material dft-fields gdsII-3d bend-flux-ll array-metadata array_metadata_SOURCES = array-metadata.cpp array_metadata_LDADD = $(MEEPLIBS) @@ -36,6 +36,9 @@ convergence_cyl_waveguide_LDADD = $(MEEPLIBS) cylindrical_SOURCES = cylindrical.cpp cylindrical_LDADD = $(MEEPLIBS) +dump_load_SOURCES = dump_load.cpp +dump_load_LDADD = $(MEEPLIBS) + flux_SOURCES = flux.cpp flux_LDADD = $(MEEPLIBS) @@ -107,7 +110,7 @@ user_defined_material_LDADD = $(MEEPLIBS) dist_noinst_DATA = cyl-ellipsoid-eps-ref.h5 array-slice-ll-ref.h5 gdsII-3d.gds -TESTS = aniso_disp bench bragg_transmission convergence_cyl_waveguide cylindrical flux harmonics integrate known_results near2far one_dimensional physical stress_tensor symmetry three_d two_dimensional 2D_convergence h5test pml +TESTS = aniso_disp bench bragg_transmission convergence_cyl_waveguide cylindrical dump_load flux harmonics integrate known_results near2far one_dimensional physical stress_tensor symmetry three_d two_dimensional 2D_convergence h5test pml if WITH_MPI LOG_COMPILER = $(RUNCODE) diff --git a/tests/dump_load.cpp b/tests/dump_load.cpp new file mode 100644 index 000000000..78685a4e9 --- /dev/null +++ b/tests/dump_load.cpp @@ -0,0 +1,232 @@ +/* Copyright (C) 2005-2021 Massachusetts Institute of Technology +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2, or (at your option) +% any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software Foundation, +% Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include + +#include +using namespace meep; +using std::complex; + +double one(const vec &) { return 1.0; } +double targets(const vec &pt) { + const double r = sqrt(pt.x() * pt.x() + pt.y() * pt.y()); + double dr = r; + while (dr > 1) dr -= 1; + if (dr > 0.7001) return 12.0; + return 1.0; +} + +#if MEEP_SINGLE +static const double tol = 1e-3, thresh = 1e-3; +#else +static const double tol = 1e-9, thresh = 1e-15; +#endif + +int compare(double a, double b, const char *n) { + if (fabs(a - b) > fabs(b) * tol && fabs(b) > thresh) { + master_printf("%s differs by\t%g out of\t%g\n", n, a - b, b); + master_printf("This gives a fractional error of %g\n", + fabs(a - b) / fabs(b)); + return 0; + } else { + return 1; + } +} + +int compare_point(fields &f1, fields &f2, const vec &p) { + monitor_point m1, m_test; + f1.get_point(&m_test, p); + f2.get_point(&m1, p); + for (int i = 0; i < 10; i++) { + component c = (component)i; + if (f1.gv.has_field(c)) { + complex v1 = m_test.get_component(c), v2 = m1.get_component(c); + if (abs(v1 - v2) > tol * abs(v2) && abs(v2) > thresh) { + master_printf("%s differs: %g %g out of %g %g\n", component_name(c), + real(v2 - v1), imag(v2 - v1), real(v2), imag(v2)); + master_printf("This comes out to a fractional error of %g\n", + abs(v1 - v2) / abs(v2)); + master_printf("Right now I'm looking at %g %g %g, time %g\n", p.x(), + p.y(), p.z(), f1.time()); + return 0; + } + } + } + return 1; +} + +int approx_point(fields &f1, fields &f2, const vec &p) { + monitor_point m1, m_test; + f1.get_point(&m_test, p); + f2.get_point(&m1, p); + for (int i = 0; i < 10; i++) { + component c = (component)i; + if (f1.gv.has_field(c)) { + complex v1 = m_test.get_component(c), v2 = m1.get_component(c); + if (abs(v1 - v2) > tol * abs(v2) && abs(v2) > thresh) { + master_printf("%s differs: %g %g out of %g %g\n", component_name(c), + real(v2 - v1), imag(v2 - v1), real(v2), imag(v2)); + master_printf("This comes out to a fractional error of %g\n", + abs(v1 - v2) / abs(v2)); + master_printf("Right now I'm looking at %g %g %g, time %g\n", p.x(), + p.y(), p.z(), f1.time()); + return 0; + } + } + } + return 1; +} + +std::string structure_dump(structure *s, const std::string &filename_prefix, + const std::string &output_name) { + std::string filename = filename_prefix + "-structure-" + output_name; + s->dump(filename.c_str()); + master_printf("Dumping structure: %s\n", filename.c_str()); + return filename; +} + +void structure_load(structure *s, const std::string &filename) { + master_printf("Loading structure: %s\n", filename.c_str()); + s->load(filename.c_str()); +} + +std::string fields_dump(fields *f, const std::string &filename_prefix, + const std::string &output_name) { + std::string filename = filename_prefix + "-fields-" + output_name; + f->dump(filename.c_str()); + master_printf("Dumping fields: %s\n", filename.c_str()); + return filename; +} + +void fields_load(fields *f, const std::string &filename) { + master_printf("Loading fields: %s\n", filename.c_str()); + f->load(filename.c_str()); +} + +int test_metal(double eps(const vec &), int splitting, const char *tmpdir) { + double a = 10.0; + double ttot = 17.0; + + grid_volume gv = vol3d(1.5, 0.5, 1.0, a); + structure s(gv, eps, no_pml(), identity(), splitting); + + std::string filename_prefix = + std::string(tmpdir) + "/test_metal_" + std::to_string(splitting); + std::string structure_filename = + structure_dump(&s, filename_prefix, "original"); + + master_printf("Metal test using %d chunks...\n", splitting); + fields f(&s); + f.add_point_source(Ez, 0.8, 0.6, 0.0, 4.0, vec(1.299, 0.299, 0.401), 1.0); + + while (f.time() < ttot) f.step(); + + std::string fields_filename = + fields_dump(&f, filename_prefix, "original"); + + structure s_load(gv, eps, no_pml(), identity(), splitting); + structure_load(&s_load, structure_filename); + + fields f_load(&s_load); + f_load.add_point_source(Ez, 0.8, 0.6, 0.0, 4.0, vec(1.299, 0.299, 0.401), + 1.0); + fields_load(&f_load, fields_filename); + + if (!compare_point(f, f_load, vec(0.5, 0.5, 0.01))) return 0; + if (!compare_point(f, f_load, vec(0.46, 0.33, 0.33))) return 0; + if (!compare_point(f, f_load, vec(1.301, 0.301, 0.399))) return 0; + if (!compare(f.field_energy(), f_load.field_energy(), " total energy")) + return 0; + if (!compare(f.electric_energy_in_box(gv.surroundings()), + f_load.electric_energy_in_box(gv.surroundings()), + "electric energy")) + return 0; + if (!compare(f.magnetic_energy_in_box(gv.surroundings()), + f_load.magnetic_energy_in_box(gv.surroundings()), + "magnetic energy")) + return 0; + return 1; +} + +int test_periodic(double eps(const vec &), int splitting, const char *tmpdir) { + double a = 10.0; + double ttot = 17.0; + + grid_volume gv = vol3d(1.5, 0.5, 1.0, a); + structure s1(gv, eps); + structure s(gv, eps, no_pml(), identity(), splitting); + + std::string filename_prefix = + std::string(tmpdir) + "/test_periodic_" + std::to_string(splitting); + std::string structure_filename = + structure_dump(&s, filename_prefix, "original"); + + master_printf("Periodic test using %d chunks...\n", splitting); + fields f(&s); + f.use_bloch(vec(0.1, 0.7, 0.3)); + f.add_point_source(Ez, 0.7, 2.5, 0.0, 4.0, vec(0.3, 0.25, 0.5), 1.0); + + while (f.time() < ttot) f.step(); + + std::string fields_filename = + fields_dump(&f, filename_prefix, "original"); + + structure s_load(gv, eps, no_pml(), identity(), splitting); + structure_load(&s_load, structure_filename); + + fields f_load(&s_load); + f_load.use_bloch(vec(0.1, 0.7, 0.3)); + f_load.add_point_source(Ez, 0.7, 2.5, 0.0, 4.0, vec(0.3, 0.25, 0.5), 1.0); + fields_load(&f_load, fields_filename); + + if (!compare_point(f, f_load, vec(0.5, 0.01, 0.5))) return 0; + if (!compare_point(f, f_load, vec(0.46, 0.33, 0.2))) return 0; + if (!compare_point(f, f_load, vec(1.0, 0.25, 0.301))) return 0; + if (!compare(f.field_energy(), f_load.field_energy(), " total energy")) + return 0; + if (!compare(f.electric_energy_in_box(gv.surroundings()), + f_load.electric_energy_in_box(gv.surroundings()), + "electric energy")) + return 0; + if (!compare(f.magnetic_energy_in_box(gv.surroundings()), + f_load.magnetic_energy_in_box(gv.surroundings()), + "magnetic energy")) + return 0; + + return 1; +} + +int main(int argc, char **argv) { + initialize mpi(argc, argv); + verbosity = 0; + + std::unique_ptr temp_dir(make_output_directory()); + master_printf("Testing 3D dump/load: temp_dir = %s...\n", temp_dir.get()); + + for (int s = 2; s < 7; s++) + if (!test_periodic(targets, s, temp_dir.get())) + abort("error in test_periodic targets\n"); + + for (int s = 2; s < 8; s++) + if (!test_metal(one, s, temp_dir.get())) + abort("error in test_metal vacuum\n"); + + delete_directory(temp_dir.get()); + return 0; +}