Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sharded fields dump #1738

Merged
merged 20 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f007420
Add support for fully local HDF5 files and shared dumping of meep::st…
kkg4theweb Jul 28, 2021
344fe55
Add support for fully local HDF5 files and shared dumping of meep::st…
kkg4theweb Jul 29, 2021
a33c062
Update python func docs
kkg4theweb Jul 29, 2021
2af4e6e
Merge branch 'NanoComp:master' into sharded-dump
kkg4theweb Jul 29, 2021
3b04bb1
Update python API documentation
kkg4theweb Aug 12, 2021
817a920
Merge branch 'master' into sharded-dump
kkg4theweb Aug 12, 2021
0f11d41
Dump/Load of 'fields'
kkg4theweb Aug 23, 2021
28b5f00
after merge
kkg4theweb Aug 23, 2021
16eb661
Save dft chunks
kkg4theweb Aug 24, 2021
6cab125
Remove debug log stmts
kkg4theweb Aug 24, 2021
c28eb2c
Add saving of time value and also reorg tests.
kkg4theweb Aug 24, 2021
42b90e0
Fix dft-chunk saving for the single-parallel-file mode.
kkg4theweb Aug 25, 2021
bdef5e8
Abort when trying to dump fields with non-null polarization state
kkg4theweb Sep 9, 2021
e500897
Clean up test_dump_load.py and add test_dump_fails_for_non_null_polar…
kkg4theweb Sep 9, 2021
cd5bd6c
Add new tests/dump_load to set of files to ignore
kkg4theweb Sep 9, 2021
004e303
Clean up the test to remove unnecessary stuff from the copied over test
kkg4theweb Sep 9, 2021
6064894
Also dump/load 'f_w_prev'
kkg4theweb Sep 9, 2021
6e3cb0d
Fix typo causing build breakage
kkg4theweb Sep 9, 2021
9294d64
load fields at the very end of init_sim after add_sources
kkg4theweb Sep 23, 2021
b8bcf9d
remove init_sim before loading fields since that now works.
kkg4theweb Sep 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions python/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,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 \
Expand Down
64 changes: 53 additions & 11 deletions python/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,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:
Expand Down Expand Up @@ -1863,29 +1864,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()
Expand All @@ -1907,18 +1927,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.

Expand All @@ -1927,8 +1951,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:
Expand Down Expand Up @@ -1958,6 +1996,10 @@ def init_sim(self):
self.loop_tile_base
)

if self.load_fields_file:
self.load_fields(
self.load_fields_file, self.load_single_parallel_file)

if self.force_all_components and self.dimensions != 1:
self.fields.require_component(mp.Ez)
self.fields.require_component(mp.Hz)
Expand Down
201 changes: 201 additions & 0 deletions python/tests/test_dump_load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
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

sim.init_sim()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this line with sim.init_sim() causes the subtest test_load_dump_fields to fail with a segmentation fault after the previously saved fields are loaded from the HDF5 file and sim.run is invoked.

Here is the output from gdb:

Running test_load_dump_fields
-----------
Initializing structure...
Halving computational cell along direction y
time for choose_chunkdivision = 0.000307183 s
Working in 2D dimensions.
Computational cell is 5 x 5 x 0 with resolution 50
     block, center = (0,0,0)
          size (1,1,1e+20)
          axes (1,0,0), (0,1,0), (0,0,1)
          dielectric constant epsilon diagonal = (10.24,10.24,10.24)
     block, center = (1,0,0)
          size (1,1,1e+20)
          axes (1,0,0), (0,1,0), (0,0,1)
          dielectric constant epsilon diagonal = (13,13,13)
time for set_epsilon = 0.0874734 s
-----------
run 0 finished at t = 15.0 (1500 timesteps)
creating epsilon from file "/tmp/meepO1ahOS/test_load_dump_fields/structure.h5" (1)...
Dumped structure to file: /tmp/meepO1ahOS/test_load_dump_fields/structure.h5 (True)
creating fields output file "/tmp/meepO1ahOS/test_load_dump_fields/fields.h5" (1)...
Fields State:
  a = 50, dt = 0.01
  m = 0, beta = 0
  t = 1500, phasein_time = 0, is_real = 1

  num_chunks = 6 (shared=1)
Dumped fields to file: /tmp/meepO1ahOS/test_load_dump_fields/fields.h5 (True)
run 1 finished at t = 20.0 (2000 timesteps)
-----------
Initializing structure...
Halving computational cell along direction y
time for choose_chunkdivision = 0.000286404 s
Working in 2D dimensions.
Computational cell is 5 x 5 x 0 with resolution 50
time for set_epsilon = 0.0759436 s
-----------
reading epsilon from file "/tmp/meepO1ahOS/test_load_dump_fields/structure.h5" (1)...
Loaded structure from file: /tmp/meepO1ahOS/test_load_dump_fields/structure.h5 (True)
reading fields from file "/tmp/meepO1ahOS/test_load_dump_fields/fields.h5" (1)...
Fields State:
  a = 50, dt = 0.01
  m = 0, beta = 0
  t = 1500, phasein_time = 0, is_real = 0

  num_chunks = 6 (shared=1)
Loaded fields from file: /tmp/meepO1ahOS/test_load_dump_fields/fields.h5 (True)
on time step 1500 (time=15), 7.26514 s/step

Thread 1 "python3.5" received signal SIGSEGV, Segmentation fault.
0x00007fffc8e6a8e0 in meep::step_curl_stride1 (f=0x55555681f940, c=meep::Bx, g1=0x0, g2=0x0, s1=0, s2=0, gv=..., 
    is=..., ie=..., dtdx=-0.5, dsig=meep::Y, sig=0x5555567505b0, kap=0x5555567503e0, siginv=0x555556750210, 
    fu=0x0, dsigu=meep::NO_DIRECTION, sigu=0x0, kapu=0x0, siginvu=0x0, dt=0.01, cnd=0x0, cndinv=0x0, fcnd=0x0)
    at step_generic_stride1.cpp:192
192	            f[i] = ((kap[k] - sig[k]) * f[i] - dtdx * (g1[i + s1] - g1[i])) * siginv[k];

The two terms in this update equation which are causing the segfault are g1[i + s1] and g1[i]. In _load_dump_fields, the g1 array corresponds to the Ez field.

As additional info, the backtrace is:

#0  0x00007fffc8e6a8e0 in meep::step_curl_stride1 (f=0x55555681f940, c=meep::Bx, g1=0x0, g2=0x0, s1=0, s2=0, 
    gv=..., is=..., ie=..., dtdx=-0.5, dsig=meep::Y, sig=0x5555567505b0, kap=0x5555567503e0, 
    siginv=0x555556750210, fu=0x0, dsigu=meep::NO_DIRECTION, sigu=0x0, kapu=0x0, siginvu=0x0, dt=0.01, cnd=0x0, 
    cndinv=0x0, fcnd=0x0) at step_generic_stride1.cpp:192
#1  0x00007fffc8e03647 in meep::fields_chunk::step_db (this=0x5555567c4e10, ft=meep::B_stuff) at step_db.cpp:119
#2  0x00007fffc8e02961 in meep::fields::step_db (this=0x555556757c10, ft=meep::B_stuff) at step_db.cpp:36
#3  0x00007fffc8e0022e in meep::fields::step (this=0x555556757c10) at step.cpp:67
#4  0x00007fffc92ac56c in _wrap_fields_step (args=0x7fffc98d3f98) at meep-python.cxx:82395


# 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()
Loading