def test_ElectrodeArray(): with pytest.raises(TypeError): ElectrodeArray("foo") with pytest.raises(TypeError): ElectrodeArray(OrderedDict({'A1': 0})) with pytest.raises(TypeError): ElectrodeArray([0]) # Empty array: earray = ElectrodeArray([]) npt.assert_equal(earray.n_electrodes, 0) # npt.assert_equal(earray[0], None) npt.assert_equal(earray['A01'], None) with pytest.raises(TypeError): earray[PointSource(0, 0, 0)] ElectrodeArray([]) # A single electrode: earray = ElectrodeArray(PointSource(0, 1, 2)) npt.assert_equal(earray.n_electrodes, 1) npt.assert_equal(isinstance(earray[0], PointSource), True) npt.assert_equal(isinstance(earray[[0]], list), True) npt.assert_equal(isinstance(earray[[0]][0], PointSource), True) npt.assert_almost_equal(earray[0].x, 0) npt.assert_almost_equal(earray[0].y, 1) npt.assert_almost_equal(earray[0].z, 2) # Indexing: ps1, ps2 = PointSource(0, 0, 0), PointSource(1, 1, 1) earray = ElectrodeArray({'A01': ps1, 'D07': ps2}) npt.assert_equal(earray['A01'], ps1) npt.assert_equal(earray['D07'], ps2) # Slots: npt.assert_equal(hasattr(earray, '__slots__'), True) npt.assert_equal(hasattr(earray, '__dict__'), False)
def test_ProsthesisSystem(): # Invalid instantiations: with pytest.raises(ValueError): ProsthesisSystem(ElectrodeArray(PointSource(0, 0, 0)), eye='both') # Iterating over the electrode array: implant = ProsthesisSystem(PointSource(0, 0, 0)) npt.assert_equal(implant.n_electrodes, 1) npt.assert_equal(implant[0], implant.earray[0]) npt.assert_equal(implant.keys(), implant.earray.keys()) # Set a stimulus after the constructor: npt.assert_equal(implant.stim, None) implant.stim = 3 npt.assert_equal(isinstance(implant.stim, Stimulus), True) npt.assert_equal(implant.stim.shape, (1, 1)) npt.assert_equal(implant.stim.time, None) npt.assert_equal(implant.stim.electrodes, [0]) with pytest.raises(ValueError): # Wrong number of stimuli implant.stim = [1, 2] with pytest.raises(TypeError): # Invalid stim type: implant.stim = "stim"
def test_PointSource(): electrode = PointSource(0, 1, 2) npt.assert_almost_equal(electrode.x, 0) npt.assert_almost_equal(electrode.y, 1) npt.assert_almost_equal(electrode.z, 2) npt.assert_almost_equal(electrode.electric_potential(0, 1, 2, 1, 1), 1) npt.assert_almost_equal(electrode.electric_potential(0, 0, 0, 1, 1), 0.035, decimal=3)
def test_Nanduri2012Spatial(): # Nanduri2012Spatial automatically sets `atten_a`: model = Nanduri2012Spatial(engine='serial', xystep=5) # User can set `atten_a`: model.atten_a = 12345 npt.assert_equal(model.atten_a, 12345) model.build(atten_a=987) npt.assert_equal(model.atten_a, 987) # Nothing in, None out: npt.assert_equal(model.predict_percept(ArgusI()), None) # Zero in = zero out: implant = ArgusI(stim=np.zeros(16)) percept = model.predict_percept(implant) npt.assert_equal(isinstance(percept, Percept), True) npt.assert_equal(percept.shape, list(model.grid.x.shape) + [1]) npt.assert_almost_equal(percept.data, 0) # Only works for DiskElectrode arrays: with pytest.raises(TypeError): implant = ProsthesisSystem(ElectrodeArray(PointSource(0, 0, 0))) implant.stim = 1 model.predict_percept(implant) with pytest.raises(TypeError): implant = ProsthesisSystem( ElectrodeArray( [DiskElectrode(0, 0, 0, 100), PointSource(100, 100, 0)])) implant.stim = [1, 1] model.predict_percept(implant) # Multiple frames are processed independently: model = Nanduri2012Spatial(engine='serial', atten_a=14000, xystep=5, xrange=(-20, 20), yrange=(-15, 15)) model.build() percept = model.predict_percept(ArgusI(stim={'A1': [1, 2]})) npt.assert_equal(percept.shape, list(model.grid.x.shape) + [2]) pmax = percept.data.max(axis=(0, 1)) npt.assert_almost_equal(percept.data[2, 3, :], pmax) npt.assert_almost_equal(pmax[1] / pmax[0], 2.0) # Nanduri model uses a linear dva2ret conversion factor: for factor in [0.0, 1.0, 2.0]: npt.assert_almost_equal(model.retinotopy.dva2ret(factor, factor), (280.0 * factor, 280.0 * factor)) for factor in [0.0, 1.0, 2.0]: npt.assert_almost_equal( model.retinotopy.ret2dva(280.0 * factor, 280.0 * factor), (factor, factor))
def test_PointSource(): electrode = PointSource(0, 1, 2) npt.assert_almost_equal(electrode.x, 0) npt.assert_almost_equal(electrode.y, 1) npt.assert_almost_equal(electrode.z, 2) npt.assert_almost_equal(electrode.electric_potential(0, 1, 2, 1, 1), 1) npt.assert_almost_equal(electrode.electric_potential(0, 0, 0, 1, 1), 0.035, decimal=3) # Slots: npt.assert_equal(hasattr(electrode, '__slots__'), True) npt.assert_equal(hasattr(electrode, '__dict__'), False)
def test_ElectrodeArray_remove_electrode(): earray1 = ElectrodeArray([]) earray2 = ElectrodeArray([]) npt.assert_equal(earray1.n_electrodes, 0) # Can't remove electrodes from empty electrodeArray with pytest.raises(ValueError): earray1.remove_electrode(None) with pytest.raises(ValueError): earray1.remove_electrode("foo") key = [0] * 4 key[0] = 'D03' key[1] = 'A02' key[2] = 'F10' key[3] = 'E12' earray1.add_electrode(key[0], PointSource(0, 1, 2)) earray1.add_electrode(key[1], PointSource(3, 4, 5)) earray1.add_electrode(key[2], PointSource(6, 7, 8)) earray1.add_electrode(key[3], PointSource(9, 10, 11)) npt.assert_equal(earray1.n_electrodes, 4) earray2.add_electrode(key[0], PointSource(0, 1, 2)) earray2.add_electrode(key[1], PointSource(3, 4, 5)) earray2.add_electrode(key[2], PointSource(6, 7, 8)) earray2.add_electrode(key[3], PointSource(9, 10, 11)) npt.assert_equal(earray2.n_electrodes, 4) # Remove one electrode key[1] from the electrodeArray earray1.remove_electrode(key[0]) npt.assert_equal(earray1.n_electrodes, 3) # Can't remove an electrode that has been removed with pytest.raises(ValueError): earray1.remove_electrode(key[0]) # List keeps order: npt.assert_equal(earray1[0], earray1[key[1]]) npt.assert_equal(earray1[1], earray1[key[2]]) npt.assert_equal(earray1[2], earray1[key[3]]) # Other electrodes stay the same for k in [key[1], key[2], key[3]]: npt.assert_equal(earray1[k].x, earray2[k].x) npt.assert_equal(earray1[k].y, earray2[k].y) npt.assert_equal(earray1[k].z, earray2[k].z) # Remove two more electrodes from the electrodeArray # List keeps order earray1.remove_electrode(key[1]) earray1.remove_electrode(key[2]) npt.assert_equal(earray1.n_electrodes, 1) npt.assert_equal(earray1[0], earray1[key[3]]) # The last electrode stays the same for key in [key[3]]: npt.assert_equal(earray1[key].x, earray2[key].x) npt.assert_equal(earray1[key].y, earray2[key].y) npt.assert_equal(earray1[key].z, earray2[key].z)
def test_Horsager2009Model(): model = Horsager2009Model() npt.assert_equal(hasattr(model, 'has_space'), True) npt.assert_equal(model.has_space, False) npt.assert_equal(hasattr(model, 'has_time'), True) npt.assert_equal(model.has_time, True) # User can set `dt`: model.temporal.dt = 1e-5 npt.assert_almost_equal(model.dt, 1e-5) npt.assert_almost_equal(model.temporal.dt, 1e-5) model.build(dt=3e-4) npt.assert_almost_equal(model.dt, 3e-4) npt.assert_almost_equal(model.temporal.dt, 3e-4) # User cannot add more model parameters: with pytest.raises(FreezeError): model.rho = 100 # Model and TemporalModel give the same result for amp, freq in zip([136.02, 120.35, 57.71], [5, 15, 225]): stim = BiphasicPulseTrain(freq, amp, 0.075, interphase_dur=0.075, stim_dur=200, cathodic_first=True) model1 = Horsager2009Model().build() model2 = Horsager2009Temporal().build() implant = ProsthesisSystem(PointSource(0, 0, 0), stim=stim) npt.assert_almost_equal(model1.predict_percept(implant).data, model2.predict_percept(stim).data)
def test_ProsthesisSystem(): # Invalid instantiations: with pytest.raises(ValueError): ProsthesisSystem(ElectrodeArray(PointSource(0, 0, 0)), eye='both') with pytest.raises(TypeError): ProsthesisSystem(Stimulus) # Iterating over the electrode array: implant = ProsthesisSystem(PointSource(0, 0, 0)) npt.assert_equal(implant.n_electrodes, 1) npt.assert_equal(implant[0], implant.earray[0]) npt.assert_equal(implant.electrode_names, implant.earray.electrode_names) for i, e in zip(implant, implant.earray): npt.assert_equal(i, e) # Set a stimulus after the constructor: npt.assert_equal(implant.stim, None) implant.stim = 3 npt.assert_equal(isinstance(implant.stim, Stimulus), True) npt.assert_equal(implant.stim.shape, (1, 1)) npt.assert_equal(implant.stim.time, None) npt.assert_equal(implant.stim.electrodes, [0]) ax = implant.plot() npt.assert_equal(len(ax.texts), 0) npt.assert_equal(len(ax.collections), 1) with pytest.raises(ValueError): # Wrong number of stimuli implant.stim = [1, 2] with pytest.raises(TypeError): # Invalid stim type: implant.stim = "stim" # Invalid electrode names: with pytest.raises(ValueError): implant.stim = {'A1': 1} with pytest.raises(ValueError): implant.stim = Stimulus({'A1': 1}) # Safe mode requires charge-balanced pulses: with pytest.raises(ValueError): implant = ProsthesisSystem(PointSource(0, 0, 0), safe_mode=True) implant.stim = 1 # Slots: npt.assert_equal(hasattr(implant, '__slots__'), True) npt.assert_equal(hasattr(implant, '__dict__'), False)
def test_PointSource(): electrode = PointSource(0, 1, 2) npt.assert_almost_equal(electrode.x, 0) npt.assert_almost_equal(electrode.y, 1) npt.assert_almost_equal(electrode.z, 2) npt.assert_almost_equal(electrode.electric_potential(0, 1, 2, 1, 1), 1) npt.assert_almost_equal(electrode.electric_potential(0, 0, 0, 1, 1), 0.035, decimal=3) # Slots: npt.assert_equal(hasattr(electrode, '__slots__'), True) npt.assert_equal(hasattr(electrode, '__dict__'), False) # Plots: ax = electrode.plot() npt.assert_equal(len(ax.texts), 0) npt.assert_equal(len(ax.patches), 1) npt.assert_equal(isinstance(ax.patches[0], Circle), True)
def test_Horsager2009Temporal(): model = Horsager2009Temporal() # User can set their own params: model.dt = 0.1 npt.assert_equal(model.dt, 0.1) model.build(dt=1e-4) npt.assert_equal(model.dt, 1e-4) # User cannot add more model parameters: with pytest.raises(FreezeError): model.rho = 100 # Nothing in, None out: implant = ProsthesisSystem(PointSource(0, 0, 0)) npt.assert_equal(model.predict_percept(implant.stim), None) # Zero in = zero out: implant.stim = np.zeros((1, 6)) percept = model.predict_percept(implant.stim, t_percept=[0, 1, 2]) npt.assert_equal(isinstance(percept, Percept), True) npt.assert_equal(percept.shape, (1, 1, 3)) npt.assert_almost_equal(percept.data, 0) # Can't request the same time more than once (this would break the Cython # loop, because `idx_frame` is incremented after a write; also doesn't # make much sense): with pytest.raises(ValueError): implant.stim = np.ones((1, 100)) model.predict_percept(implant.stim, t_percept=[0.2, 0.2]) # Single-pulse brightness from Fig.3: model = Horsager2009Temporal().build() for amp, pdur in zip([188.077, 89.74, 10.55], [0.075, 0.15, 4.0]): stim = BiphasicPulse(amp, pdur, interphase_dur=pdur, stim_dur=200, cathodic_first=True) t_percept = np.arange(0, stim.time[-1] + model.dt / 2, model.dt) percept = model.predict_percept(stim, t_percept=t_percept) npt.assert_almost_equal(percept.data.max(), 110.3, decimal=2) # Fixed-duration brightness from Fig.4: model = Horsager2009Temporal().build() for amp, freq in zip([136.02, 120.35, 57.71], [5, 15, 225]): stim = BiphasicPulseTrain(freq, amp, 0.075, interphase_dur=0.075, stim_dur=200, cathodic_first=True) t_percept = np.arange(0, stim.time[-1] + model.dt / 2, model.dt) percept = model.predict_percept(stim, t_percept=t_percept) npt.assert_almost_equal(percept.data.max(), 36.3, decimal=2)
def test_ElectrodeArray_add_electrode(): earray = ElectrodeArray([]) npt.assert_equal(earray.n_electrodes, 0) with pytest.raises(TypeError): earray.add_electrode('A01', ElectrodeArray([])) # Add an electrode: key0 = 'A04' earray.add_electrode(key0, PointSource(0, 1, 2)) npt.assert_equal(earray.n_electrodes, 1) # Both numeric and string index should work: for key in [key0, 0]: npt.assert_equal(isinstance(earray[key], PointSource), True) npt.assert_almost_equal(earray[key].x, 0) npt.assert_almost_equal(earray[key].y, 1) npt.assert_almost_equal(earray[key].z, 2) with pytest.raises(ValueError): # Can't add the same electrode twice: earray.add_electrode(key0, PointSource(0, 1, 2)) # Add another electrode: key1 = 'A01' earray.add_electrode(key1, DiskElectrode(4, 5, 6, 7)) npt.assert_equal(earray.n_electrodes, 2) # Both numeric and string index should work: for key in [key1, 1]: npt.assert_equal(isinstance(earray[key], DiskElectrode), True) npt.assert_almost_equal(earray[key].x, 4) npt.assert_almost_equal(earray[key].y, 5) npt.assert_almost_equal(earray[key].z, 6) npt.assert_almost_equal(earray[key].r, 7) # We can also get a list of electrodes: for keys in [[key0, key1], [0, key1], [key0, 1], [0, 1]]: selected = earray[keys] npt.assert_equal(isinstance(selected, list), True) npt.assert_equal(isinstance(selected[0], PointSource), True) npt.assert_equal(isinstance(selected[1], DiskElectrode), True)
def test_ElectrodeArray_add_electrodes(): earray = ElectrodeArray([]) npt.assert_equal(earray.n_electrodes, 0) with pytest.raises(TypeError): earray.add_electrodes(None) with pytest.raises(TypeError): earray.add_electrodes("foo") # Add 2 electrodes, keep order: key = [0] * 6 key[0] = 'D03' key[1] = 'A02' earray.add_electrodes({key[0]: PointSource(0, 1, 2)}) earray.add_electrodes({key[1]: PointSource(3, 4, 5)}) npt.assert_equal(earray[0], earray[key[0]]) npt.assert_equal(earray[1], earray[key[1]]) # Can't add the same key twice: with pytest.raises(ValueError): earray.add_electrodes({key[0]: PointSource(3, 5, 7)}) # Add 2 more, now keep order: key[2] = 'F10' key[3] = 'E12' earray.add_electrodes({key[2]: PointSource(6, 7, 8)}) earray.add_electrodes({key[3]: PointSource(9, 10, 11)}) npt.assert_equal(earray[0], earray[key[0]]) npt.assert_equal(earray[1], earray[key[1]]) npt.assert_equal(earray[2], earray[key[2]]) npt.assert_equal(earray[3], earray[key[3]]) # List keeps order: earray.add_electrodes([PointSource(12, 13, 14), PointSource(15, 16, 17)]) npt.assert_equal(earray[0], earray[key[0]]) npt.assert_equal(earray[1], earray[key[1]]) npt.assert_equal(earray[2], earray[key[2]]) npt.assert_equal(earray[3], earray[key[3]]) npt.assert_equal(earray[4].x, 12) npt.assert_equal(earray[5].x, 15) # Order is preserved in for loop: for i, (key, val) in enumerate(earray.items()): npt.assert_equal(earray[i], earray[key]) npt.assert_equal(earray[i], val)
def test_Nanduri2012Model_predict_percept(): # Nothing in = nothing out: model = Nanduri2012Model(xrange=(0, 0), yrange=(0, 0), engine='serial') model.build() implant = ArgusI(stim=None) npt.assert_equal(model.predict_percept(implant), None) implant.stim = np.zeros(16) npt.assert_almost_equal(model.predict_percept(implant).data, 0) # Single-pixel model same as TemporalModel: implant = ProsthesisSystem(DiskElectrode(0, 0, 0, 100)) # implant.stim = PulseTrain(5e-6) implant.stim = BiphasicPulseTrain(20, 20, 0.45, interphase_dur=0.45) t_percept = [0, 0.01, 1.0] percept = model.predict_percept(implant, t_percept=t_percept) temp = Nanduri2012Temporal().build() temp = temp.predict_percept(implant.stim, t_percept=t_percept) npt.assert_almost_equal(percept.data, temp.data, decimal=4) # Only works for DiskElectrode arrays: with pytest.raises(TypeError): implant = ProsthesisSystem(ElectrodeArray(PointSource(0, 0, 0))) implant.stim = 1 model.predict_percept(implant) with pytest.raises(TypeError): implant = ProsthesisSystem( ElectrodeArray( [DiskElectrode(0, 0, 0, 100), PointSource(100, 100, 0)])) implant.stim = [1, 1] model.predict_percept(implant) # Requested times must be multiples of model.dt: implant = ProsthesisSystem(ElectrodeArray(DiskElectrode(0, 0, 0, 260))) # implant.stim = PulseTrain(tsample) implant.stim = BiphasicPulseTrain(20, 20, 0.45) model.temporal.dt = 0.1 with pytest.raises(ValueError): model.predict_percept(implant, t_percept=[0.01]) with pytest.raises(ValueError): model.predict_percept(implant, t_percept=[0.01, 1.0]) with pytest.raises(ValueError): model.predict_percept(implant, t_percept=np.arange(0, 0.5, 0.101)) model.predict_percept(implant, t_percept=np.arange(0, 0.5, 1.0000001)) # Can't request the same time more than once (this would break the Cython # loop, because `idx_frame` is incremented after a write; also doesn't # make much sense): with pytest.raises(ValueError): model.predict_percept(implant, t_percept=[0.2, 0.2]) # It's ok to extrapolate beyond `stim` if the `extrapolate` flag is set: model.temporal.dt = 1e-2 npt.assert_almost_equal( model.predict_percept(implant, t_percept=10000).data, 0) # Output shape must be determined by t_percept: npt.assert_equal( model.predict_percept(implant, t_percept=0).shape, (1, 1, 1)) npt.assert_equal( model.predict_percept(implant, t_percept=[0, 1]).shape, (1, 1, 2)) # Brightness vs. size (use values from Nanduri paper): model = Nanduri2012Model(xystep=0.5, xrange=(-4, 4), yrange=(-4, 4)) model.build() implant = ProsthesisSystem(ElectrodeArray(DiskElectrode(0, 0, 0, 260))) amp_th = 30 bright_th = 0.107 stim_dur = 1000.0 pdur = 0.45 t_percept = np.arange(0, stim_dur, 5) amp_factors = [1, 6] frames_amp = [] for amp_f in amp_factors: implant.stim = BiphasicPulseTrain(20, amp_f * amp_th, pdur, interphase_dur=pdur, stim_dur=stim_dur) percept = model.predict_percept(implant, t_percept=t_percept) idx_frame = np.argmax(np.max(percept.data, axis=(0, 1))) brightest_frame = percept.data[..., idx_frame] frames_amp.append(brightest_frame) npt.assert_equal([np.sum(f > bright_th) for f in frames_amp], [0, 161]) freqs = [20, 120] frames_freq = [] for freq in freqs: implant.stim = BiphasicPulseTrain(freq, 1.25 * amp_th, pdur, interphase_dur=pdur, stim_dur=stim_dur) percept = model.predict_percept(implant, t_percept=t_percept) idx_frame = np.argmax(np.max(percept.data, axis=(0, 1))) brightest_frame = percept.data[..., idx_frame] frames_freq.append(brightest_frame) npt.assert_equal([np.sum(f > bright_th) for f in frames_freq], [21, 49])
def _set_grid(self): """Private method to build the electrode grid""" n_elecs = np.prod(self.shape) rows, cols = self.shape # The user did not specify a unique naming scheme: if len(self.names) == 2: # Create electrode names, using either A-Z or 1-n: if self.name_rows.isalpha(): rws = [ chr(i) for i in range(ord(self.name_rows), ord(self.name_rows) + rows + 1) ] elif self.name_rows.isdigit(): rws = [ str(i) for i in range(int(self.name_rows), rows + int(self.name_rows)) ] else: raise ValueError("rows must be alphabetic or numeric") if self.name_cols.isalpha(): clms = [ chr(i) for i in range(ord(self.name_cols), ord(self.name_cols) + cols) ] elif self.name_cols.isdigit(): clms = [ str(i) for i in range(int(self.name_cols), cols + int(self.name_cols)) ] else: raise ValueError("Columns must be alphabetic or numeric.") # facilitating Argus I naming scheme if self.name_cols.isalpha() and not self.name_rows.isalpha(): names = [ clms[j] + rws[i] for i in range(len(rws)) for j in range(len(clms)) ] else: names = [ rws[i] + clms[j] for i in range(len(rws)) for j in range(len(clms)) ] else: if len(self.names) != n_elecs: raise ValueError("If `names` specifies more than row/column " "names, it must have %d entries, not " "%d)." % (n_elecs, len(self.names))) names = self.names if isinstance(self.z, (list, np.ndarray)): # Specify different height for every electrode in a list: z_arr = np.asarray(self.z).flatten() if z_arr.size != n_elecs: raise ValueError("If `h` is a list, it must have %d entries, " "not %d." % (n_elecs, len(self.z))) else: # If `z` is a scalar, choose same height for all electrodes: z_arr = np.ones(n_elecs, dtype=float) * self.z # Make a 2D meshgrid from x, y coordinates: # For example, cols=3 with spacing=100 should give: [-100, 0, 100] x_arr_lshift = (np.arange(cols) * self.spacing - (cols / 2.0 - 0.5) * self.spacing - self.spacing * 0.25) x_arr_rshift = (np.arange(cols) * self.spacing - (cols / 2.0 - 0.5) * self.spacing + self.spacing * 0.25) y_arr = (np.arange(rows) * math.sqrt(3) * self.spacing / 2.0 - (rows / 2.0 - 0.5) * self.spacing) x_arr_lshift, y_arr_lshift = np.meshgrid(x_arr_lshift, y_arr, sparse=False) x_arr_rshift, y_arr_rshift = np.meshgrid(x_arr_rshift, y_arr, sparse=False) # added code to interleave arrays x_arr = [] for row in range(0, rows): if row % 2 == 0: x_arr.append(x_arr_lshift[row]) else: x_arr.append(x_arr_rshift[row]) x_arr = np.array(x_arr) y_arr = y_arr_rshift # Rotate the grid: rotmat = np.array([ np.cos(self.rot), -np.sin(self.rot), np.sin(self.rot), np.cos(self.rot) ]).reshape((2, 2)) xy = np.matmul(rotmat, np.vstack((x_arr.flatten(), y_arr.flatten()))) x_arr = xy[0, :] y_arr = xy[1, :] # Apply offset to make the grid centered at (self.x, self.y): x_arr += self.x y_arr += self.y if issubclass(self.etype, DiskElectrode): if isinstance(self.r, (list, np.ndarray)): # Specify different radius for every electrode in a list: if len(self.r) != n_elecs: err_s = ("If `r` is a list, it must have %d entries, not " "%d)." % (n_elecs, len(self.r))) raise ValueError(err_s) r_arr = self.r else: # If `r` is a scalar, choose same radius for all electrodes: r_arr = np.ones(n_elecs, dtype=float) * self.r # Create a grid of DiskElectrode objects: for x, y, z, r, name in zip(x_arr, y_arr, z_arr, r_arr, names): self.add_electrode(name, DiskElectrode(x, y, z, r)) elif issubclass(self.etype, PointSource): # Create a grid of PointSource objects: for x, y, z, name in zip(x_arr, y_arr, z_arr, names): self.add_electrode(name, PointSource(x, y, z)) else: raise NotImplementedError