Repository: SPARC-X/SPARC-X-API

Issue:
many warnings listed on first import

```
>>> import sparc
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:133: UserWarning: TeX file /home/jacksund/anaconda3/envs/sparc-env/doc/sparc/.LaTeX/SQ.tex is missing! It may be a typo in the document, ignore parameters from this file.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:159: UserWarning: Cannot find the c source file "initialization.c", skip version parsing!
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:204: UserWarning: Provided a non-structured frame for parsing, skip.
  warn("Provided a non-structured frame for parsing, skip.")
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in 0.0 for odd k-point mesh
 0.5 for even k-point mesh cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in 2320.904 for gaussian 
1160.452 for fermi-dirac cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in 0.007350 for gaussian 
0.003675 for fermi-dirac cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in N_e/2 x 1.2 + 5 cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in see description cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in h^2x0.001 cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:220: UserWarning: Parameter PRECOND_RESTA_Q0 is disabled in the doc, ignore!
  warn(f"Parameter {symbol} is disabled in the doc, ignore!")
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:220: UserWarning: Parameter PRECOND_RESTA_RS is disabled in the doc, ignore!
  warn(f"Parameter {symbol} is disabled in the doc, ignore!")
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:220: UserWarning: Parameter PRECOND_FITPOW is disabled in the doc, ignore!
  warn(f"Parameter {symbol} is disabled in the doc, ignore!")
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in 0.2*TOL_SCF cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in \max(TOL_FOCKx 10,0.001) cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in 0.25 for PBE0 and HSE cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in TOL_SCFx 0.01 cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in TOL_SCFx 0.001 cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in ION_TEMP cannot converted to a numerical array, will skip conversion.
  warn(
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:220: UserWarning: Parameter ION_ELEC_EQT is disabled in the doc, ignore!
  warn(f"Parameter {symbol} is disabled in the doc, ignore!")
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:727: UserWarning: Type of NPT_NH_QMASS if not standard digit or array, mark as others.
  warn(f"Type of {symbol} if not standard digit or array, mark as others.")
/home/jacksund/anaconda3/envs/sparc-env/lib/python3.11/site-packages/sparc/docparser.py:573: UserWarning: Some fields in Generated by a linear model. cannot converted to a numerical array, will skip conversion.
  warn(
```

some of these warnings make me think the issue is with the pre-compiled sparc-x install, where I used `conda install -c conda-forge sparc-x` from the docs.

I'm going to revisit the conda toolchains section of the installation and see if that works

Discussion:
@alchem0x2A: @jacksund Thanks for the comment, it is also related to https://github.com/SPARC-X/SPARC-X-API/issues/91#issue-2888817465. I hope the PR #104 solves your concerns by silencing UserWarnings for an end-user. We'll still work on to make sure these warnings are fully resolved during documentation conversion, but they should be harmless for the moment@alchem0x2A: @jacksund I believe the new changes in upcoming v1.0.7 addresses your concerns and from suggestion of the editor I'm now closing this issue. Please feel free to re-open.

Solution patch:
diff --git a/sparc/docparser.py b/sparc/docparser.py
index a42697b..e69bdd9 100644
--- a/sparc/docparser.py
+++ b/sparc/docparser.py
@@ -41,6 +41,7 @@ class SparcDocParser(object):
         parameter_categories (list): Categories of parameters extracted.
         parameters (dict): Extracted parameters with detailed information.
         other_parameters (dict): Additional parameters not categorized.
+        suppress_warnings (bool): Whether the doc parser suppress UserWarning (may be annoying during class import)
 
     Methods:
         find_main_file(main_file_pattern): Finds the main LaTeX file based on a pattern.
@@ -61,6 +62,7 @@ class SparcDocParser(object):
         intro_file="Introduction.tex",
         params_from_intro=True,
         parse_version=True,
+        suppress_warnings=True,
     ):
         """Create the doc parser pointing to the root of the doc file of SPARC
 
@@ -79,14 +81,16 @@ class SparcDocParser(object):
             intro_file: LaTeX file for the introduction
             params_from_intro: only contain the parameters that can be parsed in `intro_file`
             parse_date: get the SPARC version by date
+            suppress_warnings: whether to silence any warnings generated by the parser
         """
+        self.suppress_warnings = suppress_warnings
+        self.params_from_intro = params_from_intro
         self.root = Path(directory)
-        self.main_file = self.find_main_file(main_file)
         self.intro_file = self.root / intro_file
+        self.main_file = self.find_main_file(main_file)
         if not self.intro_file.is_file():
             raise FileNotFoundError(f"Introduction file {intro_file} is missing!")
         self.include_files = self.get_include_files()
-        self.params_from_intro = params_from_intro
         self.parse_version(parse_version)
         self.parse_parameters()
         self.postprocess()
@@ -130,12 +134,13 @@ class SparcDocParser(object):
             if tex_file.is_file():
                 include_files.append(tex_file)
             else:
-                warn(
-                    (
-                        f"TeX file {tex_file} is missing! It may be a typo in the document, "
-                        "ignore parameters from this file."
+                if not self.suppress_warnings:
+                    warn(
+                        (
+                            f"TeX file {tex_file} is missing! It may be a typo in the document, "
+                            "ignore parameters from this file."
+                        )
                     )
-                )
         return include_files
 
     def parse_version(self, parse=True):
@@ -156,18 +161,20 @@ class SparcDocParser(object):
             return
         init_c = self.root.parents[1] / "src" / "initialization.c"
         if not init_c.is_file():
-            warn(
-                'Cannot find the c source file "initialization.c", skip version parsing!'
-            )
+            if not self.suppress_warnings:
+                warn(
+                    'Cannot find the c source file "initialization.c", skip version parsing!'
+                )
             self.version = None
             return
         text = open(init_c, "r", encoding="utf8").read()
         pattern_version = r"SPARC\s+\(\s*?version(.*?)\)"
         match = re.findall(pattern_version, text)
         if len(match) != 1:
-            warn(
-                'Parsing c source file "initialization.c" for version is unsuccessful!'
-            )
+            if not self.suppress_warnings:
+                warn(
+                    'Parsing c source file "initialization.c" for version is unsuccessful!'
+                )
             self.version = None
             return
         # We need to add more spacing matching in case the source code includes extra
@@ -201,7 +208,8 @@ class SparcDocParser(object):
         pattern_block = r"\\begin\{block\}\{(.*?)\}([\s\S]*?)\\end\{block\}"
         match_label = re.findall(pattern_label, frame, re.DOTALL | re.MULTILINE)
         if len(match_label) != 1:
-            warn("Provided a non-structured frame for parsing, skip.")
+            if not self.suppress_warnings:
+                warn("Provided a non-structured frame for parsing, skip.")
             return {}
         symbol, label = (
             convert_tex_parameter(match_label[0][0].strip()),
@@ -217,15 +225,18 @@ class SparcDocParser(object):
             # Do not parse commented-out values
 
             if (key == "type") and (content.startswith("%")):
-                warn(f"Parameter {symbol} is disabled in the doc, ignore!")
+                if not self.suppress_warnings:
+                    warn(f"Parameter {symbol} is disabled in the doc, ignore!")
                 return {}
             if key in ("example",):
                 content = convert_tex_example(content)
             param_dict[key] = content
         # Sanitize 1: Convert types
-        param_dict = sanitize_type(param_dict)
+        param_dict = sanitize_type(param_dict, suppress_warnings=self.suppress_warnings)
         # Sanitize 2: Convert default values
-        param_dict = sanitize_default(param_dict)
+        param_dict = sanitize_default(
+            param_dict, suppress_warnings=self.suppress_warnings
+        )
         # Sanitize 3: Remove TeX components in description and remark
         param_dict = sanitize_description(param_dict)
 
@@ -465,7 +476,7 @@ def convert_tex_example(text):
     return new_text
 
 
-def convert_tex_default(text, desired_type=None):
+def convert_tex_default(text, desired_type=None, suppress_warnings=False):
     """Convert default values as much as possible.
     The desire type will convert the default values
     to the closest format
@@ -515,7 +526,9 @@ def convert_tex_default(text, desired_type=None):
         elif desired_type == "string":
             converted = text
         else:
-            converted = text2value(text, desired_type)
+            converted = text2value(
+                text, desired_type, suppress_warnings=suppress_warnings
+            )
     return converted
 
 
@@ -550,7 +563,7 @@ def convert_comment(text):
     return text
 
 
-def text2value(text, desired_type):
+def text2value(text, desired_type, suppress_warnings=False):
     """Convert raw text to a desired type
 
     Arguments:
@@ -558,6 +571,7 @@ def text2value(text, desired_type):
         desired_type (str): Target data type from 'string', 'integer',
                             'integer array', 'double', 'double array',
                             'bool', 'bool array'
+        suppress_warnings (bool): Suppress UserWarning if overwhelming for end-users
     Returns:
         converted: Value converted to the desired type
     """
@@ -570,14 +584,16 @@ def text2value(text, desired_type):
     try:
         arr = np.genfromtxt(text.splitlines(), delimiter=" ", dtype=float)
         if np.isnan(arr).any():
-            warn(
-                f"Some fields in {text} cannot converted to a numerical array, will skip conversion."
-            )
+            if not suppress_warnings:
+                warn(
+                    f"Some fields in {text} cannot converted to a numerical array, will skip conversion."
+                )
             arr = None
     except Exception as e:
-        warn(
-            f"Cannot transform {text} to array, skip converting. Error message is:\n {e}"
-        )
+        if not suppress_warnings:
+            warn(
+                f"Cannot transform {text} to array, skip converting. Error message is:\n {e}"
+            )
         arr = None
 
     if arr is None:
@@ -653,7 +669,7 @@ def sanitize_description(param_dict):
     return sanitized_dict
 
 
-def sanitize_default(param_dict):
+def sanitize_default(param_dict, suppress_warnings=False):
     """Sanitize the default field
     1. Create an extra field `default_remark` that copies original default
     2. Use `convert_tex_default` to convert values as much as possible
@@ -663,12 +679,14 @@ def sanitize_default(param_dict):
     sanitized_dict = param_dict.copy()
     original_default = sanitized_dict["default"]
     sanitized_dict["default_remark"] = original_default
-    converted_default = convert_tex_default(original_default, param_dict["type"])
+    converted_default = convert_tex_default(
+        original_default, param_dict["type"], suppress_warnings=suppress_warnings
+    )
     sanitized_dict["default"] = converted_default
     return sanitized_dict
 
 
-def sanitize_type(param_dict):
+def sanitize_type(param_dict, suppress_warnings=False):
     """Sanitize the param dict so that the type are more consistent
 
     For example, if type is Double / Integer,
@@ -706,10 +724,11 @@ def sanitize_type(param_dict):
             _array_test = is_array(example_value)
             _bool_test = contain_only_bool(example_value) and contain_only_bool(default)
         except Exception as e:
-            warn(
-                f"Array conversion failed for {example_value}, ignore."
-                f"The error is {e}"
-            )
+            if not suppress_warnings:
+                warn(
+                    f"Array conversion failed for {example_value}, ignore."
+                    f"The error is {e}"
+                )
             _array_test = False  # Retain
 
         if _array_test is True:
@@ -724,7 +743,8 @@ def sanitize_type(param_dict):
     if sanitized_type is None:
         # Currently there is only one NPT_NH_QMASS has this type
         # TODO: think of a way to format a mixed array?
-        warn(f"Type of {symbol} if not standard digit or array, mark as others.")
+        if not suppress_warnings:
+            warn(f"Type of {symbol} if not standard digit or array, mark as others.")
         sanitized_type = "other"
         # TODO: how about provide a true / false type?
     sanitized_dict["type"] = sanitized_type


Test patch:
diff --git a/tests/test_docparser.py b/tests/test_docparser.py
index b3173b8..2a72790 100644
--- a/tests/test_docparser.py
+++ b/tests/test_docparser.py
@@ -113,11 +113,51 @@ def test_version_parser(fs, monkeypatch):
     assert sp.version == "2022.02.23"
 
 
+def test_suppress_warnings():
+    """Test if the majority of UserWarning-s are disabled"""
+    import warnings
+
+    # import inspect
+    from sparc.docparser import SparcDocParser
+
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter("default")
+        sp = SparcDocParser(test_doc_dir, suppress_warnings=True)
+        # Only record warnings from docparser.py
+        warnings = [
+            warning
+            for warning in w
+            if warning.category == UserWarning and "docparser.py" in warning.filename
+        ]
+
+    assert len(warnings) == 0
+
+
+def test_no_suppress_warnings():
+    """Test if the UserWarning-s are emitted"""
+    import warnings
+
+    # import inspect
+    from sparc.docparser import SparcDocParser
+
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter("default")
+        sp = SparcDocParser(test_doc_dir, suppress_warnings=False)
+        # Only record warnings from docparser.py
+        warnings = [
+            warning
+            for warning in w
+            if warning.category == UserWarning and "docparser.py" in warning.filename
+        ]
+
+    assert len(warnings) >= 0
+
+
 def test_include_files():
     """Test only include files"""
     from sparc.docparser import SparcDocParser
 
-    sp = SparcDocParser(test_doc_dir)
+    sp = SparcDocParser(test_doc_dir, suppress_warnings=False)
     with pytest.warns(UserWarning):
         sp.get_include_files()