diff --git a/src/NanoParticleTools/inputs/nanoparticle.py b/src/NanoParticleTools/inputs/nanoparticle.py index 22d0fe8..20dee38 100644 --- a/src/NanoParticleTools/inputs/nanoparticle.py +++ b/src/NanoParticleTools/inputs/nanoparticle.py @@ -140,7 +140,9 @@ def sites_in_bounds(self, if center is None: center = [0, 0, 0] - distances_from_center = np.linalg.norm(np.subtract(site_coords, center), axis=1) + distances_from_center = np.linalg.norm(np.subtract( + site_coords, center), + axis=1) return distances_from_center <= self.radius def __str__(self) -> str: @@ -333,7 +335,8 @@ def __init__(self, # Check if there are zero constraints if len(constraints) == 0: - raise ValueError('There are no constraints, this particle is empty') + raise ValueError( + 'There are no constraints, this particle is empty') self.constraints = constraints self.seed = seed if seed is not None else 0 @@ -349,8 +352,7 @@ def __init__(self, conc_by_layer_and_species = [{} for _ in self.constraints] for i, dopant_conc, _, replace_el in dopant_specification: if dopant_conc < 0: - raise ValueError( - 'Dopant concentration cannot be negative') + raise ValueError('Dopant concentration cannot be negative') if dopant_conc > 1: raise ValueError( 'Dopant concentration cannot be greater than 1') @@ -369,13 +371,27 @@ def __init__(self, if total_replaced_conc > 0: dopants_present = True if total_replaced_conc > 1: - raise ValueError( - f"Dopant concentration in constraint {layer_i}" - f" on {replaced_el} sites exceeds 100%") + if total_replaced_conc - 1 < 1e-4: + # within some tolerance, just rescale the concentrations + # This is most likely a numerical representation/rounding issue + scale_factor = 1 / (total_replaced_conc + 1e-7) + for i in range(len(dopant_specification)): + dopant_spec = dopant_specification[i] + if dopant_spec[0] == layer_i: + dopant_specification[i] = (layer_i, + dopant_spec[1] * + scale_factor, + dopant_spec[2], + dopant_spec[3]) + else: + raise ValueError( + f"Dopant concentration in constraint {layer_i}" + f" on {replaced_el} sites exceeds 100%") if not dopants_present: - raise ValueError('There are no dopants being placed, this is an empty particle.' - 'The result will be zero intensity for everything') + raise ValueError( + 'There are no dopants being placed, this is an empty particle.' + 'The result will be zero intensity for everything') self.prune_hosts = prune_hosts if prune_hosts: diff --git a/tests/inputs/test_nanoparticle.py b/tests/inputs/test_nanoparticle.py index d665064..3e4f4f7 100644 --- a/tests/inputs/test_nanoparticle.py +++ b/tests/inputs/test_nanoparticle.py @@ -63,6 +63,33 @@ def test_doped_nanoparticle(): assert len(dnp.sites) == 56 +def test_doped_near_one(): + with pytest.raises(ValueError): + constraints = [SphericalConstraint(10)] + dopants = [(0, 0.1, 'Yb', 'Y'), (0, 1, 'Er', 'Y')] + dnp = DopedNanoparticle(constraints, dopants) + + with pytest.raises(ValueError): + constraints = [SphericalConstraint(10)] + dopants = [(0, 2e-4, 'Yb', 'Y'), (0, 1, 'Er', 'Y')] + dnp = DopedNanoparticle(constraints, dopants) + + constraints = [SphericalConstraint(10)] + dopants = [(0, 0.33334, 'Yb', 'Y'), (0, 0.666667, 'Er', 'Y')] + dnp = DopedNanoparticle(constraints, dopants) + assert dnp.dopant_specification[0][1] == pytest.approx(0.333337, abs=1e-6) + assert dnp.dopant_specification[1][1] == pytest.approx(0.666662, abs=1e-6) + + constraints = [SphericalConstraint(10), SphericalConstraint(20)] + dopants = [(0, 0.33334, 'Yb', 'Y'), (0, 0.666667, 'Er', 'Y'), + (1, .20001, 'Yb', 'Y'), (1, 0.8, 'Er', 'Y')] + dnp = DopedNanoparticle(constraints, dopants) + assert dnp.dopant_specification[0][1] == pytest.approx(0.333337, abs=1e-6) + assert dnp.dopant_specification[1][1] == pytest.approx(0.666662, abs=1e-6) + assert dnp.dopant_specification[2][1] == pytest.approx(0.200007, abs=1e-6) + assert dnp.dopant_specification[3][1] == pytest.approx(0.799992, abs=1e-6) + + def test_nanoparticle_with_empty(): constraints = [ SphericalConstraint(40),