Repository: einsteinpy/einsteinpy

Issue:
Create custom Error class for coordinate operations
<!--

WELCOME ABOARD!

Hi and welcome to the einsteinpy project. We appreciate bug reports, questions
about documentation, and suggestions for new features.

IMPORTANT: If you are reporting a problem with einsteinpy, please follow the
template below. If it's a suggestion, a possible new addition to the library,
or just want to say "Thank you!", you can remove all this text and start
from scratch.

-->
<!--- Provide a general summary of the issue in the Title above -->

🐞 **What bugs you?**

Since some coordinate operations (conversion, calculating 4-Velocity, scalar product et al) depend on the metric tensor, it is imperative that we have the metric class (specifically these methods: `g_cov_` & `_dg_dx_` or alternatively, `_ch_sym_`) defined in the corresponding coordinates. The issue of new coordinates & metric classes is to be tackled by #525. However, we will not be able to have all metric classes defined in all coordinate systems and in such cases, some operations (basically all 4-Vector operations) will be blocked. In those cases, we should display (a) proper error message(s) to the user. It will be useful to define a custom Error class for the same. Currently, we are using `TypeError` and `NotImplementedError` at corresponding places, but we should consolidate these into a single `CoordinateError` or better-named error class, with (an) easy to follow error message(s).

🎯 **Goal**

<!--- Why is this change important to you? How would you use it? -->
<!--- How can it benefit other users? -->
To provide clarity around coordinate classes and feasibility of some coordinate operations to the end user.

💡 **Possible solutions**

<!--- Not obligatory, but suggest an idea for implementing addition or change -->
Add a new Error class to `coordinates.utils`, taking into account the aforementioned use case.

📋  **Steps to solve the problem**

 * Comment below about what you've started working on.
 * Add, commit, push your changes
 * Submit a pull request and add this in comments - `Addresses #<put issue number here>`
 * Ask for a review in comments section of pull request
 * Celebrate your contribution to this project 🎉


Discussion:
pep8speaks: Hello @Aditya7799! Thanks for opening this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:

* In the file [`src/einsteinpy/coordinates/differential.py`](https://github.com/einsteinpy/einsteinpy/blob/e6e2060254c136b63f31cd829fcb71453dc09d4e/src/einsteinpy/coordinates/differential.py):

> [Line 118:26](https://github.com/einsteinpy/einsteinpy/blob/e6e2060254c136b63f31cd829fcb71453dc09d4e/src/einsteinpy/coordinates/differential.py#L118): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 119:53](https://github.com/einsteinpy/einsteinpy/blob/e6e2060254c136b63f31cd829fcb71453dc09d4e/src/einsteinpy/coordinates/differential.py#L119): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace

* In the file [`src/einsteinpy/coordinates/utils.py`](https://github.com/einsteinpy/einsteinpy/blob/e6e2060254c136b63f31cd829fcb71453dc09d4e/src/einsteinpy/coordinates/utils.py):

> [Line 8:1](https://github.com/einsteinpy/einsteinpy/blob/e6e2060254c136b63f31cd829fcb71453dc09d4e/src/einsteinpy/coordinates/utils.py#L8): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1
> [Line 9:22](https://github.com/einsteinpy/einsteinpy/blob/e6e2060254c136b63f31cd829fcb71453dc09d4e/src/einsteinpy/coordinates/utils.py#L9): [E231](https://duckduckgo.com/?q=pep8%20E231) missing whitespace after ','
> [Line 12:1](https://github.com/einsteinpy/einsteinpy/blob/e6e2060254c136b63f31cd829fcb71453dc09d4e/src/einsteinpy/coordinates/utils.py#L12): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1
> [Line 13:22](https://github.com/einsteinpy/einsteinpy/blob/e6e2060254c136b63f31cd829fcb71453dc09d4e/src/einsteinpy/coordinates/utils.py#L13): [E231](https://duckduckgo.com/?q=pep8%20E231) missing whitespace after ','










Aditya7799: The build is expected to fail on the tests where `NotImplemeted` and `TypeError` were previously raised. 
JeS24: @Aditya7799 There is a recent `matplotlib` / `mpl_toolkits` change that renames some attributes. Use [this](https://github.com/matplotlib/matplotlib/issues/4067#issuecomment-753666340) to fix that `AttributeError`.
JeS24: Of course, you need to make changes to the tests and also write some tests for the two new classes.
Aditya7799: > Of course, you need to make changes to the tests and also write some tests for the two new classes.
I have made the required changes to the existing pytests. What kind of tests should I write for the two new classes?

codecov[bot]: # [Codecov](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551?src=pr&el=h1) Report
> Merging [#551](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551?src=pr&el=desc) (afd1d11) into [master](https://codecov.io/gh/einsteinpy/einsteinpy/commit/c3498327a3f0aca5595eeb0ed452c8350d0a544e?el=desc) (c349832) will **decrease** coverage by `0.19%`.
> The diff coverage is `86.48%`.

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

```diff
@@            Coverage Diff             @@
##           master     #551      +/-   ##
==========================================
- Coverage   96.86%   96.66%   -0.20%     
==========================================
  Files          65       65              
  Lines        2199     2218      +19     
==========================================
+ Hits         2130     2144      +14     
- Misses         69       74       +5     
```


| [Impacted Files](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/einsteinpy/coordinates/utils.py](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551/diff?src=pr&el=tree#diff-c3JjL2VpbnN0ZWlucHkvY29vcmRpbmF0ZXMvdXRpbHMucHk=) | `95.49% <64.28%> (-4.51%)` | :arrow_down: |
| [src/einsteinpy/coordinates/differential.py](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551/diff?src=pr&el=tree#diff-c3JjL2VpbnN0ZWlucHkvY29vcmRpbmF0ZXMvZGlmZmVyZW50aWFsLnB5) | `100.00% <100.00%> (ø)` | |
| [src/einsteinpy/metric/base\_metric.py](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551/diff?src=pr&el=tree#diff-c3JjL2VpbnN0ZWlucHkvbWV0cmljL2Jhc2VfbWV0cmljLnB5) | `100.00% <100.00%> (ø)` | |
| [src/einsteinpy/metric/kerr.py](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551/diff?src=pr&el=tree#diff-c3JjL2VpbnN0ZWlucHkvbWV0cmljL2tlcnIucHk=) | `100.00% <100.00%> (ø)` | |
| [src/einsteinpy/metric/kerrnewman.py](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551/diff?src=pr&el=tree#diff-c3JjL2VpbnN0ZWlucHkvbWV0cmljL2tlcnJuZXdtYW4ucHk=) | `100.00% <100.00%> (ø)` | |
| [src/einsteinpy/metric/schwarzschild.py](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551/diff?src=pr&el=tree#diff-c3JjL2VpbnN0ZWlucHkvbWV0cmljL3NjaHdhcnpzY2hpbGQucHk=) | `100.00% <100.00%> (ø)` | |
| [src/einsteinpy/plotting/geodesic/static.py](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551/diff?src=pr&el=tree#diff-c3JjL2VpbnN0ZWlucHkvcGxvdHRpbmcvZ2VvZGVzaWMvc3RhdGljLnB5) | `100.00% <100.00%> (ø)` | |
| [src/einsteinpy/utils/\_\_init\_\_.py](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551/diff?src=pr&el=tree#diff-c3JjL2VpbnN0ZWlucHkvdXRpbHMvX19pbml0X18ucHk=) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551?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/einsteinpy/einsteinpy/pull/551?src=pr&el=footer). Last update [c349832...afd1d11](https://codecov.io/gh/einsteinpy/einsteinpy/pull/551?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

JeS24: > > Of course, you need to make changes to the tests and also write some tests for the two new classes.
> 
> I have made the required changes to the existing pytests. What kind of tests should I write for the two new classes?

Tests are usually written to validate the flow of logic. In our case, given classes similar to [here](https://stackoverflow.com/a/60465422/11922029), you can simply test if the `type` matches and if the error strings match some expected value.
JeS24: You can run `pytest --cov-report=term-missing --cov=einsteinpy` to run all tests and get coverage info for the entire package (Or add specific paths to run it for individual files).
JeS24: @Aditya7799 A few things to note:
* #547 was just merged and it added `__all__` to most `__init__.py` files. We will need to update `__all__` with these new classes too. I recommend, either syncing your local with upstream or just creating a new clone and transferring over these changes - whatever seems suitable to you.
* It's better to move the error classes to a separate `exceptions.py` in `einsteinpy/utils/`.
* Import these classes in `einsteinpy/utils/__init__.py` (also update `__all__` there).

Regarding https://github.com/einsteinpy/einsteinpy/pull/551#discussion_r554543481 :
* You can test if the outputs from `str` and `repr` match.
* Try passing a bunch of objects of various types to the constructor and see, if the final output matches their overall string representation.
Aditya7799: @JeS24 Sorry I keep failing one or the other test XP. This PR has become very clustered. Any suggestions or should I just make a fresh PR after all changes have been reviewed/finalized. 
JeS24: After the changes in the review, this is how the docstring would look:
![image](https://user-images.githubusercontent.com/31299799/104160715-93779200-5417-11eb-8de3-f1ec0e1d5fdc.png)

Aditya7799: @JeS24  Is the project coverage lesser than master because new files were added? 
JeS24: > @JeS24 Is the project coverage lesser than master because new files were added?

I am not sure. Let me check. Meanwhile, update the [CHANGELOG](https://github.com/Aditya7799/einsteinpy/blob/8dda47e8363b81ab61d2fd5f34c2ab3aefec006b/CHANGELOG#L35), with something like:
```
- [#551]: Fixed #526, Exceptions module added, CoordinateError class implemented
```

Also, add your GitHub name below Qbiwan in the same file.
JeS24: CodeCov is considering `utils/scalar_factor` for some reason. The [full coverage report](https://codecov.io/api/gh/einsteinpy/einsteinpy/download/build?path=v4/raw/2021-01-10/CE03EAFA9C00D3F82EEB9A35C14F3DB2/b9e825d26238d75e979b4f7d56d331d13ebe78fb/f8bde131-2cc6-4486-b771-66ec666161f1.txt) is not helping either.
JeS24: @Aditya7799 I have attempted a patch for the coverage issues around `scalar_factor`. Try syncing your local with upstream, as you've done before, and push. It should fix this issue.
JeS24: @Aditya7799 Alright, now we need to squash and rebase. Try [this](https://github.com/einsteinpy/einsteinpy/wiki/How-to-refactor-commits).
JeS24: > Hello @Aditya7799! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:
> 
> * In the file [`tests/test_utils/test_exceptions.py`](https://github.com/einsteinpy/einsteinpy/blob/98ccd09e26314a6cf179302b867435c3a530306f/tests/test_utils/test_exceptions.py):
> 
> > [Line 13:121](https://github.com/einsteinpy/einsteinpy/blob/98ccd09e26314a6cf179302b867435c3a530306f/tests/test_utils/test_exceptions.py#L13): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (148 > 120 characters)
> > [Line 14:121](https://github.com/einsteinpy/einsteinpy/blob/98ccd09e26314a6cf179302b867435c3a530306f/tests/test_utils/test_exceptions.py#L14): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (130 > 120 characters)
> > [Line 16:35](https://github.com/einsteinpy/einsteinpy/blob/98ccd09e26314a6cf179302b867435c3a530306f/tests/test_utils/test_exceptions.py#L16): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> > [Line 18:37](https://github.com/einsteinpy/einsteinpy/blob/98ccd09e26314a6cf179302b867435c3a530306f/tests/test_utils/test_exceptions.py#L18): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> 
> ##### Comment last updated at 2021-01-11 13:34:50 UTC

Okay, these are the last changes to be made. You can either `git amend` or make a normal commit. Up to you. Also, you can simply run `black` on this file to fix these issues, instead of doing it manually, though there are not many issues to fix.

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index 1bc61a1..5844234 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,7 @@ CONTRIBUTORS
 - bibek22
 - JeS24
 - Qbiwan
+- Aditya7799
 
 CHANGES
 - [#506]: Tests moved outside of the package. 
@@ -32,6 +33,7 @@ CHANGES
 - [#547]: Fixed #516, Added __all__ across modules
 - [#547]: Fixed #552, Renamed missing attributes in einsteinpy.plotting.geodesic.static
 - [#547]: Increased test coverage for einsteinpy.ijit
+- [#551]: Fixed #526, Exceptions module added, CoordinateError class implemented
 
 0.5.0
 -----
diff --git a/docs/source/api/utils/exceptions.rst b/docs/source/api/utils/exceptions.rst
new file mode 100644
index 0000000..80481b7
--- /dev/null
+++ b/docs/source/api/utils/exceptions.rst
@@ -0,0 +1,5 @@
+Exceptions module
+=================
+
+.. automodule:: einsteinpy.utils.exceptions
+    :members:
diff --git a/docs/source/api/utils/utils_index.rst b/docs/source/api/utils/utils_index.rst
index 016eed3..ceefa66 100644
--- a/docs/source/api/utils/utils_index.rst
+++ b/docs/source/api/utils/utils_index.rst
@@ -7,3 +7,4 @@ The common utilities of the project are included in this module.
     :maxdepth: 2
 
     scalar_factor
+    exceptions
diff --git a/src/einsteinpy/coordinates/differential.py b/src/einsteinpy/coordinates/differential.py
index 3043754..3736f56 100644
--- a/src/einsteinpy/coordinates/differential.py
+++ b/src/einsteinpy/coordinates/differential.py
@@ -8,6 +8,7 @@ from einsteinpy.coordinates.conversion import (
     SphericalConversion,
 )
 from einsteinpy.coordinates.utils import v0
+from einsteinpy.utils import CoordinateError
 
 _c = constant.c.value
 
@@ -106,15 +107,15 @@ class CartesianDifferential(CartesianConversion):
 
         Raises
         ------
-        TypeError
+        CoordinateError
             If ``metric`` object has been instantiated with a coordinate system, \
             other than Cartesian Coordinates.
 
         """
         g = args[0]
         if self.system != g.coords.system:
-            raise TypeError(
-                "Metric object has been instantiated with a coordinate system,"
+            raise CoordinateError(
+                f"Metric object has been instantiated with a coordinate system, ( {g.coords.system} )"
                 " other than Cartesian Coordinates."
             )
 
@@ -318,15 +319,15 @@ class SphericalDifferential(SphericalConversion):
 
         Raises
         ------
-        TypeError
+        CoordinateError
             If ``metric`` object has been instantiated with a coordinate system, \
-            other than Cartesian Coordinates.
+            other than Sperical Polar Coordinates.
 
         """
         g = args[0]
         if self.system != g.coords.system:
-            raise TypeError(
-                "Metric object has been instantiated with a coordinate system,"
+            raise CoordinateError(
+                f"Metric object has been instantiated with a coordinate system, ( {g.coords.system} )"
                 " other than Spherical Polar Coordinates."
             )
 
@@ -530,15 +531,15 @@ class BoyerLindquistDifferential(BoyerLindquistConversion):
 
         Raises
         ------
-        TypeError
+        CoordinateError
             If ``metric`` object has been instantiated with a coordinate system, \
-            other than Cartesian Coordinates.
+            other than Boyer-Lindquist Coordinates.
 
         """
         g = args[0]
         if self.system != g.coords.system:
-            raise TypeError(
-                "Metric object has been instantiated with a coordinate system,"
+            raise CoordinateError(
+                "Metric object has been instantiated with a coordinate system, ( {g.coords.system} )"
                 " other than Boyer-Lindquist Coordinates."
             )
 
diff --git a/src/einsteinpy/metric/base_metric.py b/src/einsteinpy/metric/base_metric.py
index db0da8a..0cfba69 100644
--- a/src/einsteinpy/metric/base_metric.py
+++ b/src/einsteinpy/metric/base_metric.py
@@ -25,6 +25,7 @@ from astropy import units as u
 
 from einsteinpy import constant
 from einsteinpy.units import primitive
+from einsteinpy.utils import CoordinateError
 
 _c = constant.c.value
 _G = constant.G.value
@@ -283,7 +284,7 @@ class BaseMetric:
 
         Raises
         ------
-        NotImplementedError
+        CoordinateError
             If ``einsteinpy.metric.*`` does not have the metric in the \
             coordinate system, the metric object has been instantiated with
 
@@ -322,7 +323,7 @@ class BaseMetric:
                     "outer_ergosphere": _out_ergo,
                 }
 
-            raise NotImplementedError(
+            raise CoordinateError(
                 "Singularities for Kerr solutions are only available in"
                 "Boyer-Lindquist Coordinates."
             )
@@ -335,7 +336,7 @@ class BaseMetric:
                 "outer_ergosphere": r_s,
             }
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "Singularities for Schwarzschild Metric are only available in"
             "Spherical Polar Coordinates."
         )
diff --git a/src/einsteinpy/metric/kerr.py b/src/einsteinpy/metric/kerr.py
index 68fa424..8ff5b0a 100644
--- a/src/einsteinpy/metric/kerr.py
+++ b/src/einsteinpy/metric/kerr.py
@@ -3,6 +3,7 @@ from astropy import units as u
 
 from einsteinpy import constant
 from einsteinpy.metric import BaseMetric
+from einsteinpy.utils import CoordinateError
 
 _c = constant.c.value
 
@@ -93,15 +94,15 @@ class Kerr(BaseMetric):
 
         Raises
         ------
-        NotImplementedError
-            In case of the metric is not available in \
+        CoordinateError
+            Raised, if the metric is not available in \
             the supplied Coordinate System
 
         """
         if self.coords.system == "BoyerLindquist":
             return self._g_cov_bl(x_vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "Kerr Metric is available only in Boyer-Lindquist Coordinates."
         )
 
@@ -228,15 +229,15 @@ class Kerr(BaseMetric):
 
         Raises
         ------
-        NotImplementedError
-            In case of the Christoffel Symbols are not \
+        CoordinateError
+            Raised, if the Christoffel symbols are not \
             available in the supplied Coordinate System
 
         """
         if self.coords.system == "BoyerLindquist":
             return self._ch_sym_bl(x_vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "Christoffel Symbols for Kerr Metric are available only in Boyer-Lindquist Coordinates."
         )
 
@@ -305,15 +306,15 @@ class Kerr(BaseMetric):
 
         Raises
         ------
-        NotImplementedError
-            In case of ``f_vec`` is not available in \
+        CoordinateError
+            Raised, if ``f_vec`` is not available in \
             the supplied Coordinate System
 
         """
         if self.coords.system == "BoyerLindquist":
             return self._f_vec_bl(lambda_, vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "'f_vec' for Kerr Metric is available only in Boyer-Lindquist Coordinates."
         )
 
diff --git a/src/einsteinpy/metric/kerrnewman.py b/src/einsteinpy/metric/kerrnewman.py
index e2aa02a..e6b7c9f 100644
--- a/src/einsteinpy/metric/kerrnewman.py
+++ b/src/einsteinpy/metric/kerrnewman.py
@@ -3,6 +3,7 @@ from astropy import units as u
 
 from einsteinpy import constant
 from einsteinpy.metric import BaseMetric
+from einsteinpy.utils import CoordinateError
 
 _c = constant.c.value
 _G = constant.G.value
@@ -102,15 +103,15 @@ class KerrNewman(BaseMetric):
 
         Raises
         ------
-        NotImplementedError
-            In case of the metric is not available in \
+        CoordinateError
+            Raised, if the metric is not available in \
             the supplied Coordinate System
 
         """
         if self.coords.system == "BoyerLindquist":
             return self._g_cov_bl(x_vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "Kerr-Newman Metric is available only in Boyer-Lindquist Coordinates."
         )
 
@@ -256,15 +257,15 @@ class KerrNewman(BaseMetric):
 
         Raises
         ------
-        NotImplementedError
-            In case of the Christoffel Symbols are not \
+        CoordinateError
+            Raised, if the Christoffel Symbols are not \
             available in the supplied Coordinate System
 
         """
         if self.coords.system == "BoyerLindquist":
             return self._ch_sym_bl(x_vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "Christoffel Symbols for Kerr-Newman Metric are available only in Boyer-Lindquist Coordinates."
         )
 
@@ -333,15 +334,15 @@ class KerrNewman(BaseMetric):
 
         Raises
         ------
-        NotImplementedError
-            In case of ``f_vec`` is not available in \
+        CoordinateError
+            Raised, if ``f_vec`` is not available in \
             the supplied Coordinate System
 
         """
         if self.coords.system == "BoyerLindquist":
             return self._f_vec_bl(lambda_, vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "'f_vec' for Kerr-Newman Metric is available only in Boyer-Lindquist Coordinates."
         )
 
diff --git a/src/einsteinpy/metric/schwarzschild.py b/src/einsteinpy/metric/schwarzschild.py
index 34d3a37..6744918 100644
--- a/src/einsteinpy/metric/schwarzschild.py
+++ b/src/einsteinpy/metric/schwarzschild.py
@@ -3,6 +3,7 @@ from astropy import units as u
 
 from einsteinpy import constant
 from einsteinpy.metric import BaseMetric
+from einsteinpy.utils import CoordinateError
 
 _c = constant.c.value
 
@@ -55,7 +56,7 @@ class Schwarzschild(BaseMetric):
         if self.coords.system == "Spherical":
             return self._g_cov_s(x_vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "Schwarzschild Metric is available only in Spherical Polar Coordinates."
         )
 
@@ -106,15 +107,15 @@ class Schwarzschild(BaseMetric):
 
         Raises
         ------
-        NotImplementedError
-            In case of the Christoffel Symbols are not \
+        CoordinateError
+            Raised, if the Christoffel Symbols are not \
             available in the supplied Coordinate System
 
         """
         if self.coords.system == "Spherical":
             return self._ch_sym_s(x_vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "Christoffel Symbols for Schwarzschild Metric are available only in Spherical Polar Coordinates."
         )
 
@@ -173,15 +174,15 @@ class Schwarzschild(BaseMetric):
 
         Raises
         ------
-        NotImplementedError
-            In case of ``f_vec`` is not available in \
+        CoordinateError
+            Raised, if ``f_vec`` is not available in \
             the supplied Coordinate System
 
         """
         if self.coords.system == "Spherical":
             return self._f_vec_s(lambda_, vec)
 
-        raise NotImplementedError(
+        raise CoordinateError(
             "'f_vec' for Schwarzschild Metric is available only in Spherical Polar Coordinates."
         )
 
diff --git a/src/einsteinpy/utils/__init__.py b/src/einsteinpy/utils/__init__.py
index 699b9d7..b371db7 100644
--- a/src/einsteinpy/utils/__init__.py
+++ b/src/einsteinpy/utils/__init__.py
@@ -1,1 +1,4 @@
+from .exceptions import BaseError, CoordinateError
 from .scalar_factor import scalar_factor, scalar_factor_derivative
+
+__all__ = ["BaseError", "CoordinateError"]
diff --git a/src/einsteinpy/utils/exceptions.py b/src/einsteinpy/utils/exceptions.py
new file mode 100644
index 0000000..5b9de5e
--- /dev/null
+++ b/src/einsteinpy/utils/exceptions.py
@@ -0,0 +1,58 @@
+"""Docstring for exceptions.py module
+
+This module defines the ``BaseError`` class which is the base class for all \
+custom Errors in EinsteinPy, and the ``CoordinateError`` class, which is a child class \
+used for raising exceptions when the geometry does not support \
+the supplied coordinate system.
+
+"""
+
+
+class BaseError(Exception):
+    """
+    Base class for custom errors
+
+    """
+
+    def __init__(self, *args, **kwargs):
+        """
+        | Constructor
+        | Joins ``args`` into a ``message`` string
+
+        Parameters
+        ------------
+        *args : iterable
+            Other arguments
+        **kwargs : dict
+            Keyword arguments
+
+        """
+        self.message = " ".join(str(i) for i in args)
+
+    def __repr__(self):
+        return f"{self.__class__.__name__} : {self.message}"
+
+    def __str__(self):
+        return self.__repr__()
+
+
+class CoordinateError(BaseError):
+    """
+    Error class for invalid coordinate operations
+
+    """
+
+    def __init__(self, *args, **kwargs):
+        """
+        | Constructor
+        | Joins ``args`` into a ``message`` string
+
+        Parameters
+        ------------
+        *args : iterable
+            Other arguments
+        **kwargs : dict
+            Keyword arguments
+
+        """
+        super().__init__(*args, **kwargs)


Test patch:
diff --git a/tests/test_coordinates/test_diff_transform.py b/tests/test_coordinates/test_diff_transform.py
index 4af0637..800f41b 100644
--- a/tests/test_coordinates/test_diff_transform.py
+++ b/tests/test_coordinates/test_diff_transform.py
@@ -14,6 +14,7 @@ from einsteinpy.coordinates import (
 )
 from einsteinpy.metric import BaseMetric, Kerr, Schwarzschild
 from einsteinpy import constant
+from einsteinpy.utils import CoordinateError
 
 _c = constant.c.value
 
@@ -223,7 +224,7 @@ def test_velocity2(spherical_differential, bl_differential):
     assert_allclose(bd.velocity(metric=mk)[1:], [bd.v_r.value, bd.v_th.value, bd.v_p.value])
 
 
-def test_v_t_raises_TypeError(cartesian_differential, spherical_differential, bl_differential):
+def test_v_t_raises_CoordinateError(cartesian_differential, spherical_differential, bl_differential):
     M = 1e24 * u.kg
     a = 0. * u.one
     ms = Schwarzschild(coords=spherical_differential, M=M)
@@ -243,16 +244,16 @@ def test_v_t_raises_TypeError(cartesian_differential, spherical_differential, bl
     def sd_k(sd, mk):
         return sd.velocity(metric=mk)
 
-    with pytest.raises(TypeError):
+    with pytest.raises(CoordinateError):
         cd_s(cd, ms)
 
-    with pytest.raises(TypeError):
+    with pytest.raises(CoordinateError):
         bd_s(bd, ms)
 
-    with pytest.raises(TypeError):
+    with pytest.raises(CoordinateError):
         cd_k(cd, mk)
 
-    with pytest.raises(TypeError):
+    with pytest.raises(CoordinateError):
         sd_k(sd, mk)
 
 
@@ -268,17 +269,17 @@ def test_v_t_getter_setter0(cartesian_differential, spherical_differential, bl_d
     def sd_vt(spherical_differential, mk):
         spherical_differential.v_t = (mk,)
 
-    # This should not raise TypeError
+    # This should not raise CoordinateError
     try:
         spherical_differential.v_t = (ms,)
-    except TypeError:
+    except CoordinateError:
         pytest.fail("Unexpected TypeError!")
 
-    # These 2 should raise TypeError
-    with pytest.raises(TypeError):
+    # These 2 should raise CoordinateError
+    with pytest.raises(CoordinateError):
         cd_vt(cartesian_differential, ms)
 
-    with pytest.raises(TypeError):
+    with pytest.raises(CoordinateError):
         sd_vt(spherical_differential, mk)
 
 
@@ -291,14 +292,14 @@ def test_v_t_getter_setter1(cartesian_differential, spherical_differential, bl_d
     def bd_vt(bl_differential, ms):
         bl_differential.v_t = (ms,)
 
-    # This should not raise TypeError
+    # This should not raise CoordinateError
     try:
         bl_differential.v_t = (mk,)
-    except TypeError:
+    except CoordinateError:
         pytest.fail("Unexpected TypeError!")
 
     # This should raise TypeError
-    with pytest.raises(TypeError):
+    with pytest.raises(CoordinateError):
         bd_vt(bl_differential, ms)
 
 
diff --git a/tests/test_metric/test_base_metric.py b/tests/test_metric/test_base_metric.py
index 2308b99..d16eef2 100644
--- a/tests/test_metric/test_base_metric.py
+++ b/tests/test_metric/test_base_metric.py
@@ -10,6 +10,7 @@ from einsteinpy import constant
 from einsteinpy.coordinates import SphericalDifferential, BoyerLindquistDifferential
 
 from einsteinpy.metric import BaseMetric, Kerr, KerrNewman, Schwarzschild
+from einsteinpy.utils.exceptions import CoordinateError
 
 _c = constant.c.value
 
@@ -119,7 +120,7 @@ def dummy_input(sph, bl):
 
 def test_coordinate_mismatch0(dummy_input):
     """
-    Tests, if NotImplementedError is raised, in case of coordinate system mismatch, \
+    Tests, if CoordinateError is raised, in case of coordinate system mismatch, \
     between metric coordinates and supplied coordinates
 
     """
@@ -137,22 +138,22 @@ def test_coordinate_mismatch0(dummy_input):
     def test_ms_con(ms):
         ms_cov = ms.metric_contravariant(x_vec_sph)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_ms_cov(ms)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_mk_cov(mk)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_mkn_cov(mkn)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_ms_con(ms)
 
 
 def test_coordinate_mismatch1(dummy_input):
     """
-    Tests, if NotImplementedError is raised, in case of coordinate system mismatch, \
+    Tests, if CoordinateError is raised, in case of coordinate system mismatch, \
     between metric coordinates and supplied coordinates
 
     """
@@ -170,22 +171,22 @@ def test_coordinate_mismatch1(dummy_input):
     def test_ms_fvec(ms):
         ms_fvec = ms.f_vec(0., x_vec_sph)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_ms_chl(ms)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_mk_chl(mk)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_mkn_chl(mkn)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_ms_fvec(ms)
 
 
 def test_coordinate_mismatch2(dummy_input):
     """
-    Tests, if NotImplementedError is raised, in case of coordinate system mismatch, \
+    Tests, if CoordinateError is raised, in case of coordinate system mismatch, \
     between metric coordinates and supplied coordinates
 
     """
@@ -203,16 +204,16 @@ def test_coordinate_mismatch2(dummy_input):
     def test_mkn_fvec(mkn):
         ms_fvec = mkn.f_vec(0., x_vec_bl)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_mk_con(mk)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_mkn_con(mkn)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_mk_fvec(mk)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         test_mkn_fvec(mkn)
 
 
@@ -380,9 +381,9 @@ def test_singularities_for_uncharged_nonrotating_case(sph, bl):
     assert_allclose(mksinglist[2], scr, rtol=1e-4, atol=0.0)
 
 
-def test_singularities_raises_NotImplementedError(sph, bl):
+def test_singularities_raises_CoordinateError(sph, bl):
     """
-    Tests, if ``singularities()`` raises a NotImplementedError, \
+    Tests, if ``singularities()`` raises a CoordinateError, \
     when there is a coordinate mismatch between supplied coordinate \
     object and the coordinate object, metric was instantiated with
 
@@ -406,13 +407,13 @@ def test_singularities_raises_NotImplementedError(sph, bl):
     def mknsing(mkn):
         mknsing = mkn.singularities()
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         mssing(ms)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         mksing(mk)
 
-    with pytest.raises(NotImplementedError):
+    with pytest.raises(CoordinateError):
         mknsing(mkn)
 
 
diff --git a/tests/test_utils/test_exceptions.py b/tests/test_utils/test_exceptions.py
new file mode 100644
index 0000000..cdf8dbe
--- /dev/null
+++ b/tests/test_utils/test_exceptions.py
@@ -0,0 +1,43 @@
+import pytest
+
+from einsteinpy.utils import CoordinateError
+
+
+def test_CoordinateError_class():
+    """
+    Tests, if the errors raised with the CoordinateError class \
+    ``einsteinpy.coordinates.utils.CoordinateError`` are \
+    appropriate
+
+    """
+    err_string = " ".join(
+        str(i)
+        for i in [
+            "Error",
+            404,
+            chr(0xA),
+            "\bPage Found =",
+            False,
+            "\b, Please contact :",
+            11.223344,
+            None,
+            0x1F76,
+            12e-4,
+        ]
+    )
+    err = CoordinateError(
+        "Error",
+        404,
+        chr(0xA),
+        "\bPage Found =",
+        False,
+        "\b, Please contact :",
+        11.223344,
+        None,
+        0x1F76,
+        12e-4,
+    )
+
+    assert hasattr(err, "message")
+    assert str(err) == repr(err)
+    assert err_string == err.message