Repository: PlasmaPy/PlasmaPy
Pull request #862 (2020-06-29 02:02:59)

Allow CustomParticle to accept string represenations of astropy Quantity's
This update gives `CustomParticle` the ability to accept string representations of astropy `Quantity`'s for input parameters `mass` and `charge`.  This looks like...

```python
par = CustomParticle(mass="5. kg", charge = "6. C")
```

This functionality is needed to generalize/streamline the serialization and deserialization of particles being developed in PR #836.

codecov[bot]: # [Codecov](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862?src=pr&el=h1) Report
> Merging [#862](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862?src=pr&el=desc) into [master](https://codecov.io/gh/PlasmaPy/PlasmaPy/commit/983c2f841d7690643e9568b71e649bb4859ff70f&el=desc) will **increase** coverage by `0.03%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/graphs/tree.svg?width=650&height=150&src=pr&token=LzZ5mnXVad)](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #862      +/-   ##
==========================================
+ Coverage   96.00%   96.04%   +0.03%     
==========================================
  Files          59       59              
  Lines        5209     5180      -29     
==========================================
- Hits         5001     4975      -26     
+ Misses        208      205       -3     
```


| [Impacted Files](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [plasmapy/particles/particle\_class.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvcGFydGljbGVzL3BhcnRpY2xlX2NsYXNzLnB5) | `96.40% <100.00%> (+0.65%)` | :arrow_up: |
| [plasmapy/plasma/plasma\_base.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvcGxhc21hL3BsYXNtYV9iYXNlLnB5) | `67.74% <0.00%> (-4.49%)` | :arrow_down: |
| [plasmapy/simulation/abstractions.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvc2ltdWxhdGlvbi9hYnN0cmFjdGlvbnMucHk=) | `75.00% <0.00%> (-2.78%)` | :arrow_down: |
| [plasmapy/plasma/sources/openpmd\_hdf5.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvcGxhc21hL3NvdXJjZXMvb3BlbnBtZF9oZGY1LnB5) | `78.40% <0.00%> (-1.17%)` | :arrow_down: |
| [plasmapy/simulation/particletracker.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvc2ltdWxhdGlvbi9wYXJ0aWNsZXRyYWNrZXIucHk=) | `98.27% <0.00%> (-0.03%)` | :arrow_down: |
| [plasmapy/particles/ionization\_state.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvcGFydGljbGVzL2lvbml6YXRpb25fc3RhdGUucHk=) | `92.93% <0.00%> (-0.03%)` | :arrow_down: |
| [plasmapy/utils/pytest\_helpers/pytest\_helpers.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvdXRpbHMvcHl0ZXN0X2hlbHBlcnMvcHl0ZXN0X2hlbHBlcnMucHk=) | `93.38% <0.00%> (-0.03%)` | :arrow_down: |
| [plasmapy/formulary/braginskii.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvZm9ybXVsYXJ5L2JyYWdpbnNraWkucHk=) | `99.72% <0.00%> (-0.01%)` | :arrow_down: |
| [plasmapy/plasma/sources/plasma3d.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvcGxhc21hL3NvdXJjZXMvcGxhc21hM2QucHk=) | `100.00% <0.00%> (ø)` | |
| [plasmapy/utils/decorators/checks.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree#diff-cGxhc21hcHkvdXRpbHMvZGVjb3JhdG9ycy9jaGVja3MucHk=) | `100.00% <0.00%> (ø)` | |
| ... and [5 more](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862?src=pr&el=footer). Last update [983c2f8...3acb551](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/862?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/changelog/862.trivial.rst b/changelog/862.trivial.rst
new file mode 100644
index 00000000..95d8678e
--- /dev/null
+++ b/changelog/862.trivial.rst
@@ -0,0 +1,2 @@
+Update :class:`~plasmapy.particles.particle_class.CustomParticle` so input parameters
+`mass` and `charge` can accept string representations of astropy `Quantities`.
diff --git a/plasmapy/particles/particle_class.py b/plasmapy/particles/particle_class.py
index 179dfe37..60fc1452 100644
--- a/plasmapy/particles/particle_class.py
+++ b/plasmapy/particles/particle_class.py
@@ -1832,15 +1832,18 @@ def charge(self) -> u.C:
     @mass.setter
     def mass(self, m: u.kg):
         if m is None:
-            self._mass = np.nan * u.kg
+            m = np.nan * u.kg
             warnings.warn("CustomParticle mass set to NaN kg", MissingAtomicDataWarning)
+        elif isinstance(m, str):
+            m = u.Quantity(m)
         elif not isinstance(m, u.Quantity):
             raise TypeError(
                 "The mass of a custom particle must be a nonnegative Quantity "
                 "with units of mass."
             )
+        if np.isnan(m):
+            self._mass = m
         else:
-
             if not isinstance(m.value, Real):
                 raise TypeError(
                     "The mass of a custom particle must be a real number "
@@ -1858,10 +1861,15 @@ def mass(self, m: u.kg):
     @charge.setter
     def charge(self, q: Optional[Union[u.Quantity, Real]]):
         if q is None:
-            self._charge = np.nan * u.C
+            q = np.nan * u.C
             warnings.warn(
                 "CustomParticle charge set to NaN C", MissingAtomicDataWarning
             )
+        elif isinstance(q, str):
+            q = u.Quantity(q)
+
+        if np.isnan(q):
+            self._charge = q
         elif isinstance(q, Real):
             self._charge = q * const.e.si
             warnings.warn(


Test patch:
diff --git a/plasmapy/particles/tests/test_particle_class.py b/plasmapy/particles/tests/test_particle_class.py
index 25ae4b92..89dd7c16 100644
--- a/plasmapy/particles/tests/test_particle_class.py
+++ b/plasmapy/particles/tests/test_particle_class.py
@@ -905,7 +905,9 @@ def test_that_object_can_be_dict_key(key):
     (CustomParticle, {"charge": -0.1 * u.C}, "charge", -0.1 * u.C),
     (CustomParticle, {"charge": -2}, "charge", -2 * const.e.si),
     (CustomParticle, {"mass": np.inf * u.g}, "mass", np.inf * u.kg),
+    (CustomParticle, {"mass": "100.0 g"}, "mass", 100.0 * u.g),
     (CustomParticle, {"charge": -np.inf * u.kC}, "charge", -np.inf * u.C),
+    (CustomParticle, {"charge": "5.0 C"}, "charge", 5.0 * u.C),
 ]
 
 
@@ -947,12 +949,16 @@ def test_customized_particles(cls, kwargs, attr, expected):
     (CustomParticle, {"charge": np.complex128(5 + 2j)}, InvalidParticleError),
     (CustomParticle, {"mass": np.complex128(5 + 2j)}, InvalidParticleError),
     (CustomParticle, {"mass": -1e-36 * u.kg}, InvalidParticleError),
+    (CustomParticle, {"mass": "not a mass"}, InvalidParticleError),
+    (CustomParticle, {"mass": "5.0 km"}, InvalidParticleError),
     (CustomParticle, {"mass": np.array([1, 1]) * u.kg}, InvalidParticleError),
     (CustomParticle, {"charge": np.array([1, 1]) * u.C}, InvalidParticleError),
     (CustomParticle, {"charge": (5 + 2j) * u.C}, InvalidParticleError),
     (CustomParticle, {"mass": (5 + 2j) * u.kg}, InvalidParticleError),
     (CustomParticle, {"charge": np.complex128(5 + 2j) * u.C}, InvalidParticleError),
     (CustomParticle, {"mass": np.complex128(5 + 2j) * u.kg}, InvalidParticleError),
+    (CustomParticle, {"charge": "not a charge"}, InvalidParticleError),
+    (CustomParticle, {"charge": "5.0 km"}, InvalidParticleError),
 ]