diff --git a/openpnm/core/Base.py b/openpnm/core/Base.py index 6d37d6c701..1871329434 100644 --- a/openpnm/core/Base.py +++ b/openpnm/core/Base.py @@ -1165,10 +1165,7 @@ def interleave_data(self, prop): >>> print(g1['pore.label']) # 'pore.label' is defined on pn, not g1 [False False False False] """ - element = self._parse_element(prop.split('.')[0], single=True) - N = self.project.network._count(element) - - # Fetch sources list depending on object type? + # Fetch sources list depending on type of self proj = self.project if self._isa() in ['network', 'geometry']: sources = list(proj.geometries().values()) @@ -1179,13 +1176,45 @@ def interleave_data(self, prop): else: raise Exception('Unrecognized object type, cannot find dependents') + # Get generalized element and array length + element = self._parse_element(prop.split('.')[0], single=True) + N = self.project.network._count(element) + # Attempt to fetch the requested array from each object - arrs = [item.get(prop, None) for item in sources] + arrs = [obj.get(prop, None) for obj in sources] + + # Check for missing sources, and add None to arrs if necessary + if N > sum([obj._count(element) for obj in sources]): + arrs.append(None) + + # Obtain list of locations for inserting values locs = [self._get_indices(element, item.name) for item in sources] - sizes = [np.size(a) for a in arrs] + if np.all([item is None for item in arrs]): # prop not found anywhere raise KeyError(prop) + # -------------------------------------------------------------------- + # Let's start by handling the easy cases first + if not any([a is None for a in arrs]): + # All objs present and array found on all objs + shape = list(arrs[0].shape) + shape[0] = N + types = [a.dtype for a in arrs] + if len(set(types)) == 1: + # All types are the same + temp_arr = np.ones(shape, dtype=types[0]) + for vals, inds in zip(arrs, locs): + temp_arr[inds] = vals + return temp_arr # Return early because it's just easier + elif all([a.dtype in [float, int, bool] for a in arrs]): + # All types are numeric, make float + temp_arr = np.ones(shape, dtype=float) + for vals, inds in zip(arrs, locs): + temp_arr[inds] = vals + return temp_arr # Return early because it's just easier + + # --------------------------------------------------------------------- + # Now handle the complicated cases # Check the general type of each array atype = [] for a in arrs: @@ -1211,6 +1240,7 @@ def interleave_data(self, prop): temp_arr = np.zeros((N, item.shape[1]), dtype=item.dtype) temp_arr.fill(dummy_val[atype[0]]) + sizes = [np.size(a) for a in arrs] # Convert int arrays to float IF NaNs are expected if temp_arr.dtype.name.startswith('int') and \ (np.any([i is None for i in arrs]) or np.sum(sizes) != N): @@ -1228,16 +1258,15 @@ def interleave_data(self, prop): # Importing unyt significantly adds to our import time, we also # currently don't use this package extensively, so we're not going # to support it for now. - """ - if any([hasattr(a, 'units') for a in arrs]): - [a.convert_to_mks() for a in arrs if hasattr(a, 'units')] - units = [a.units.__str__() for a in arrs if hasattr(a, 'units')] - if len(units) > 0: - if len(set(units)) == 1: - temp_arr *= np.array([1]) * getattr(unyt, units[0]) - else: - raise Exception('Units on the interleaved array are not equal') - """ + + # if any([hasattr(a, 'units') for a in arrs]): + # [a.convert_to_mks() for a in arrs if hasattr(a, 'units')] + # units = [a.units.__str__() for a in arrs if hasattr(a, 'units')] + # if len(units) > 0: + # if len(set(units)) == 1: + # temp_arr *= np.array([1]) * getattr(unyt, units[0]) + # else: + # raise Exception('Units on the interleaved array are not equal') return temp_arr diff --git a/tests/unit/core/BaseTest.py b/tests/unit/core/BaseTest.py index a68fe71340..a20443c1e1 100644 --- a/tests/unit/core/BaseTest.py +++ b/tests/unit/core/BaseTest.py @@ -712,95 +712,6 @@ def test_map_pores_missing(self): b = self.geo22.map_pores(pores=Ps, origin=self.net2) assert len(b) == 0 - def test_interleave_data_bool(self): - net = op.network.Cubic(shape=[2, 2, 2]) - Ps = net.pores('top') - geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) - Ps = net.pores('bottom') - geom2 = op.geometry.GenericGeometry(network=net, pores=Ps) - # Ensure Falses return in missing places - geom1['pore.blah'] = True - assert np.all(~geom2['pore.blah']) - assert np.sum(net['pore.blah']) == 4 - # Ensure all Trues returned now - geom2['pore.blah'] = True - assert np.all(geom2['pore.blah']) - assert np.sum(net['pore.blah']) == 8 - - def test_interleave_data_int(self): - net = op.network.Cubic(shape=[2, 2, 2]) - Ps = net.pores('top') - geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) - Ps = net.pores('bottom') - geom2 = op.geometry.GenericGeometry(network=net, pores=Ps) - geom1['pore.blah'] = 1 - # Ensure ints are returned geom1 - assert 'int' in geom1['pore.blah'].dtype.name - # Ensure nans are returned on geom2 - assert np.all(np.isnan(geom2['pore.blah'])) - # Ensure interleaved array is float with nans - assert 'float' in net['pore.blah'].dtype.name - # Ensure missing values are floats - assert np.sum(np.isnan(net['pore.blah'])) == 4 - - def test_interleave_data_float(self): - net = op.network.Cubic(shape=[2, 2, 2]) - Ps = net.pores('top') - geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) - Ps = net.pores('bottom') - geom2 = op.geometry.GenericGeometry(network=net, pores=Ps) - geom1['pore.blah'] = 1.0 - # Ensure flaots are returned geom1 - assert 'float' in geom1['pore.blah'].dtype.name - # Ensure nans are returned on geom2 - assert np.all(np.isnan(geom2['pore.blah'])) - # Ensure interleaved array is float with nans - assert 'float' in net['pore.blah'].dtype.name - # Ensure missing values are floats - assert np.sum(np.isnan(net['pore.blah'])) == 4 - - def test_interleave_data_object(self): - net = op.network.Cubic(shape=[2, 2, 2]) - Ps = net.pores('top') - geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) - Ps = net.pores('bottom') - _ = op.geometry.GenericGeometry(network=net, pores=Ps) - geom1['pore.blah'] = [[1, 2], [1, 2, 3], [1, 2, 3, 4], [1]] - assert 'object' in net['pore.blah'].dtype.name - # Ensure missing elements are None - assert np.sum([item is None for item in net['pore.blah']]) == 4 - - def test_interleave_data_key_error(self): - net = op.network.Cubic(shape=[2, 2, 2]) - Ps = net.pores('top') - geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) - Ps = net.pores('bottom') - geom2 = op.geometry.GenericGeometry(network=net, pores=Ps) - with pytest.raises(KeyError): - net['pore.blah'] - with pytest.raises(KeyError): - geom1['pore.blah'] - with pytest.raises(KeyError): - geom2['pore.blah'] - - def test_interleave_data_float_missing_geometry(self): - net = op.network.Cubic(shape=[2, 2, 2]) - geom = op.geometry.GenericGeometry(network=net, pores=[0, 1, 2]) - geom['pore.blah'] = 1.0 - assert np.any(np.isnan(net['pore.blah'])) - - def test_interleave_data_int_missing_geometry(self): - net = op.network.Cubic(shape=[2, 2, 2]) - geom = op.geometry.GenericGeometry(network=net, pores=[0, 1, 2]) - geom['pore.blah'] = 1 - assert np.any(np.isnan(net['pore.blah'])) - - def test_interleave_data_bool_missing_geometry(self): - net = op.network.Cubic(shape=[2, 2, 2]) - geom = op.geometry.GenericGeometry(network=net, pores=[0, 1, 2]) - geom['pore.blah'] = True - assert np.sum(net['pore.blah']) == geom.Np - def test_getitem_with_no_matches(self): self.geo.pop('pore.blah', None) with pytest.raises(KeyError): diff --git a/tests/unit/core/SubdomainTest.py b/tests/unit/core/SubdomainTest.py index 4b36589fc0..7a9aa784df 100644 --- a/tests/unit/core/SubdomainTest.py +++ b/tests/unit/core/SubdomainTest.py @@ -1,6 +1,5 @@ import pytest import numpy as np -import scipy as sp import openpnm as op @@ -75,6 +74,215 @@ def test_drop_locations_all_but_not_complete(self): assert self.phase1.num_throats(self.phys1.name) == 0 self.phys1._add_locations(pores=self.net.Ps, throats=self.net.Ts) + def test_interleaving_missing_objects(self): + pn = op.network.Cubic(shape=[3, 1, 1]) + geo1 = op.geometry.GenericGeometry(network=pn, pores=[0], throats=[0]) + geo2 = op.geometry.GenericGeometry(network=pn, pores=[1], throats=[1]) + + geo1['pore.test_float'] = 1.0 + geo2['pore.test_float'] = 2.0 + assert pn['pore.test_float'].dtype == float + assert np.isnan(pn['pore.test_float']).sum() == 1 + + geo1['pore.test_int'] = 1 + geo2['pore.test_int'] = 2 + assert pn['pore.test_int'].dtype == float + assert np.isnan(pn['pore.test_int']).sum() == 1 + + geo1['pore.test_bool'] = True + geo2['pore.test_bool'] = False + assert pn['pore.test_bool'].dtype == bool + assert pn['pore.test_bool'].sum() == 1 + + geo1['pore.test_int'] = 1.0 + geo2['pore.test_int'] = 2 + assert pn['pore.test_int'].dtype == float + assert np.isnan(pn['pore.test_int']).sum() == 1 + + geo1['pore.test_int'] = 1 + geo2['pore.test_int'] = 2.0 + assert pn['pore.test_int'].dtype == float + assert np.isnan(pn['pore.test_int']).sum() == 1 + + def test_interleaving_mixed_data(self): + pn = op.network.Cubic(shape=[3, 1, 1]) + geo1 = op.geometry.GenericGeometry(network=pn, pores=[0], throats=[0]) + geo2 = op.geometry.GenericGeometry(network=pn, pores=[1], throats=[1]) + geo3 = op.geometry.GenericGeometry(network=pn, pores=[2]) + + # Test floats with all arrays present + geo1['pore.test_float'] = 1.0 + geo2['pore.test_float'] = 2.0 + geo3['pore.test_float'] = 3.0 + assert pn['pore.test_float'].dtype == float + assert np.isnan(pn['pore.test_float']).sum() == 0 + + # Test mixed datatype with all arrays present + # It's not clear that we want this behavior + # geo1['pore.test_mixed'] = 1.0 + # geo2['pore.test_mixed'] = 2 + # geo3['pore.test_mixed'] = False + # assert pn['pore.test_mixed'].dtype == float + # assert np.isnan(pn['pore.test_mixed']).sum() == 0 + + # Check heterogeneous datatypes + geo1['pore.test_mixed'] = 1 + geo2['pore.test_mixed'] = 2 + geo3['pore.test_mixed'] = 3.0 + assert pn['pore.test_mixed'].dtype == float + + # Make sure order doesn't matter + geo1['pore.test_mixed'] = 1.0 + geo2['pore.test_mixed'] = 2 + geo3['pore.test_mixed'] = 3 + assert pn['pore.test_mixed'].dtype == float + + def test_interleaving_partial_data(self): + pn = op.network.Cubic(shape=[3, 1, 1]) + geo1 = op.geometry.GenericGeometry(network=pn, pores=[0], throats=[0]) + geo2 = op.geometry.GenericGeometry(network=pn, pores=[1], throats=[1]) + geo3 = op.geometry.GenericGeometry(network=pn, pores=[2]) + + # Test ints with a missing array + geo1['pore.test_int_missing'] = 1 + geo2['pore.test_int_missing'] = 2 + assert np.isnan(pn['pore.test_int_missing']).sum() == 1 + assert pn['pore.test_int_missing'].dtype == float + + # Test ints with all arrays present + geo1['pore.test_int'] = 1 + geo2['pore.test_int'] = 2 + geo3['pore.test_int'] = 3 + assert pn['pore.test_int'].dtype == int + + # Test booleans with a missing array + geo1['pore.test_bool'] = True + geo2['pore.test_bool'] = False + assert pn['pore.test_bool'].dtype == bool + assert pn['pore.test_bool'].sum() == 1 + + def test_interleave_data_bool(self): + net = op.network.Cubic(shape=[2, 2, 2]) + Ps = net.pores('top') + geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) + Ps = net.pores('bottom') + geom2 = op.geometry.GenericGeometry(network=net, pores=Ps) + # Ensure Falses return in missing places + geom1['pore.blah'] = True + assert np.all(~geom2['pore.blah']) + assert np.sum(net['pore.blah']) == 4 + # Ensure all Trues returned now + geom2['pore.blah'] = True + assert np.all(geom2['pore.blah']) + assert np.sum(net['pore.blah']) == 8 + + def test_interleave_data_int(self): + net = op.network.Cubic(shape=[2, 2, 2]) + Ps = net.pores('top') + geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) + Ps = net.pores('bottom') + geom2 = op.geometry.GenericGeometry(network=net, pores=Ps) + geom1['pore.blah'] = 1 + # Ensure ints are returned geom1 + assert 'int' in geom1['pore.blah'].dtype.name + # Ensure nans are returned on geom2 + assert np.all(np.isnan(geom2['pore.blah'])) + # Ensure interleaved array is float with nans + assert 'float' in net['pore.blah'].dtype.name + # Ensure missing values are floats + assert np.sum(np.isnan(net['pore.blah'])) == 4 + + def test_interleave_data_float(self): + net = op.network.Cubic(shape=[2, 2, 2]) + Ps = net.pores('top') + geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) + Ps = net.pores('bottom') + geom2 = op.geometry.GenericGeometry(network=net, pores=Ps) + geom1['pore.blah'] = 1.0 + # Ensure flaots are returned geom1 + assert 'float' in geom1['pore.blah'].dtype.name + # Ensure nans are returned on geom2 + assert np.all(np.isnan(geom2['pore.blah'])) + # Ensure interleaved array is float with nans + assert 'float' in net['pore.blah'].dtype.name + # Ensure missing values are floats + assert np.sum(np.isnan(net['pore.blah'])) == 4 + + def test_interleave_data_object(self): + net = op.network.Cubic(shape=[2, 2, 2]) + Ps = net.pores('top') + geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) + Ps = net.pores('bottom') + _ = op.geometry.GenericGeometry(network=net, pores=Ps) + geom1['pore.blah'] = [[1, 2], [1, 2, 3], [1, 2, 3, 4], [1]] + assert 'object' in net['pore.blah'].dtype.name + # Ensure missing elements are None + assert np.sum([item is None for item in net['pore.blah']]) == 4 + + def test_interleave_data_key_error(self): + net = op.network.Cubic(shape=[2, 2, 2]) + Ps = net.pores('top') + geom1 = op.geometry.GenericGeometry(network=net, pores=Ps) + Ps = net.pores('bottom') + geom2 = op.geometry.GenericGeometry(network=net, pores=Ps) + with pytest.raises(KeyError): + net['pore.blah'] + with pytest.raises(KeyError): + geom1['pore.blah'] + with pytest.raises(KeyError): + geom2['pore.blah'] + + def test_interleave_data_float_missing_geometry(self): + net = op.network.Cubic(shape=[2, 2, 2]) + geom = op.geometry.GenericGeometry(network=net, pores=[0, 1, 2]) + geom['pore.blah'] = 1.0 + assert np.any(np.isnan(net['pore.blah'])) + + def test_interleave_data_int_missing_geometry(self): + net = op.network.Cubic(shape=[2, 2, 2]) + geom = op.geometry.GenericGeometry(network=net, pores=[0, 1, 2]) + geom['pore.blah'] = 1 + assert np.any(np.isnan(net['pore.blah'])) + + def test_interleave_data_bool_missing_geometry(self): + net = op.network.Cubic(shape=[2, 2, 2]) + geom = op.geometry.GenericGeometry(network=net, pores=[0, 1, 2]) + geom['pore.blah'] = True + assert np.sum(net['pore.blah']) == geom.Np + + def test_interleave_data_float_missing_physics(self): + net = op.network.Cubic(shape=[4, 1, 1]) + geo1 = op.geometry.GenericGeometry(network=net, pores=[0, 1], + throats=[0, 1]) + geo2 = op.geometry.GenericGeometry(network=net, pores=[2, 3], + throats=[2]) + air = op.phases.Air(network=net) + phys = op.physics.GenericPhysics(network=net, phase=air, geometry=geo1) + phys['pore.blah'] = 1.0 + assert np.any(np.isnan(air['pore.blah'])) + + def test_interleave_data_int_missing_physics(self): + net = op.network.Cubic(shape=[4, 1, 1]) + geo1 = op.geometry.GenericGeometry(network=net, pores=[0, 1], + throats=[0, 1]) + geo2 = op.geometry.GenericGeometry(network=net, pores=[2, 3], + throats=[2]) + air = op.phases.Air(network=net) + phys = op.physics.GenericPhysics(network=net, phase=air, geometry=geo1) + phys['pore.blah'] = 1 + assert np.any(np.isnan(air['pore.blah'])) + + def test_interleave_data_bool_missing_physics(self): + net = op.network.Cubic(shape=[4, 1, 1]) + geo1 = op.geometry.GenericGeometry(network=net, pores=[0, 1], + throats=[0, 1]) + geo2 = op.geometry.GenericGeometry(network=net, pores=[2, 3], + throats=[2]) + air = op.phases.Air(network=net) + phys = op.physics.GenericPhysics(network=net, phase=air, geometry=geo1) + phys['pore.blah'] = True + assert np.sum(air['pore.blah']) == phys.Np + def test_writting_subdict_names_across_subdomains(self): ws = op.Workspace() proj = ws.new_project() diff --git a/tests/unit/models/physics/DiffusiveConductanceTest.py b/tests/unit/models/physics/DiffusiveConductanceTest.py index baebb77d3e..d2cc9eea05 100644 --- a/tests/unit/models/physics/DiffusiveConductanceTest.py +++ b/tests/unit/models/physics/DiffusiveConductanceTest.py @@ -87,7 +87,7 @@ def test_taylor_aris_diffusion(self): self.geo['throat.conduit_lengths.pore1'] = 0.15 self.geo['throat.conduit_lengths.throat'] = 0.6 self.geo['throat.conduit_lengths.pore2'] = 0.25 - self.phase['pore.pressure'] = sp.linspace(0, 20, self.net.Np) + self.phase['pore.pressure'] = np.linspace(0, 20, self.net.Np) self.phase['throat.hydraulic_conductance'] = 1 mod = op.models.physics.diffusive_conductance.taylor_aris_diffusion self.phys.add_model(propname='throat.ta_diffusive_conductance',