SincCity 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sinccity/__init__.py ADDED
@@ -0,0 +1,42 @@
1
+ """Generic Scattering Calculator: I(q) of a real-space structure.
2
+
3
+ A notebook equivalent of the nuclear, 1D part of SasView's Generic Scattering
4
+ Calculator: load a PDB, GRO, SLD or VTK file, compute the orientationally averaged
5
+ I(q) by the exact Debye formula, and show the points and the curve. The
6
+ result converts to a sasdata ``Data1D``, which is how it reaches a fit (for
7
+ example ``IQuana.set_data``).
8
+
9
+ Example:
10
+ >>> from sinccity import GenericScatteringCalculator
11
+ >>> gsc = GenericScatteringCalculator()
12
+ >>> gsc.load_structure('particle.sld')
13
+ >>> gsc.plot_points()
14
+ >>> gsc.set_params(sld_solvent=6.34) # 10^-6 1/Ang^2, as in sasmodels
15
+ >>> profile = gsc.calculate()
16
+ >>> gsc.plot_profile()
17
+ >>> data = profile.to_data1d() # a Data1D for IQuana.set_data
18
+ """
19
+
20
+ __version__ = '0.2.0'
21
+
22
+ from ._console import set_verbosity
23
+ from .calculator import GenericScatteringCalculator, ScatteringProfile
24
+ from .debye import CalculationInterrupted, DistanceSpectrum
25
+ from .model import RgResult, Structure, StructureError
26
+ from .readers import load, read_gro, read_pdb, read_sld, read_vtk
27
+
28
+ __all__ = [
29
+ 'CalculationInterrupted',
30
+ 'DistanceSpectrum',
31
+ 'GenericScatteringCalculator',
32
+ 'RgResult',
33
+ 'ScatteringProfile',
34
+ 'Structure',
35
+ 'StructureError',
36
+ 'load',
37
+ 'read_gro',
38
+ 'read_pdb',
39
+ 'read_sld',
40
+ 'read_vtk',
41
+ 'set_verbosity',
42
+ ]
sinccity/_console.py ADDED
@@ -0,0 +1,115 @@
1
+ """Console output: the package logger and encoding-safe glyphs.
2
+
3
+ Status messages that are a side effect of a call (structure loaded, q grid
4
+ set, profile computed) go through ``logging.getLogger('sinccity')`` instead of
5
+ ``print``, so a caller can turn them off::
6
+
7
+ import sinccity
8
+ sinccity.set_verbosity('quiet')
9
+
10
+ The logger owns a stdout handler and does not propagate, so by default the
11
+ messages simply appear and do not duplicate into an application's own logging
12
+ configuration. To hand them over to that configuration instead::
13
+
14
+ import logging
15
+ log = logging.getLogger('sinccity')
16
+ log.handlers.clear()
17
+ log.propagate = True
18
+
19
+ Tables the user asks for explicitly (``describe()``) still ``print``: they
20
+ *are* the result of the call rather than a side effect of it.
21
+
22
+ The glyph constants fall back to ASCII when the active stdout cannot encode
23
+ them. A legacy Windows console (cp1252) has no ``✓`` or ``⁻``, and printing
24
+ them there raises ``UnicodeEncodeError``.
25
+ """
26
+
27
+ import logging
28
+ import sys
29
+ from typing import Any
30
+
31
+ LOGGER_NAME = 'sinccity'
32
+
33
+ logger = logging.getLogger(LOGGER_NAME)
34
+
35
+
36
+ class _StdoutHandler(logging.StreamHandler):
37
+ """A ``StreamHandler`` that resolves ``sys.stdout`` at emit time.
38
+
39
+ ``StreamHandler`` binds its stream at construction, which would miss every
40
+ later redirection: the Jupyter kernel's streams, pytest's capture,
41
+ ``contextlib.redirect_stdout``. Resolving per emit keeps the logger
42
+ behaving like a ``print`` call.
43
+ """
44
+
45
+ @property
46
+ def stream(self) -> Any:
47
+ return sys.stdout
48
+
49
+ @stream.setter
50
+ def stream(self, value: Any) -> None:
51
+ """Ignore the stream ``StreamHandler.__init__`` tries to bind."""
52
+
53
+
54
+ def _install_default_handler() -> None:
55
+ handler = _StdoutHandler()
56
+ handler.setFormatter(logging.Formatter('%(message)s'))
57
+ logger.addHandler(handler)
58
+ logger.setLevel(logging.INFO)
59
+ logger.propagate = False
60
+
61
+
62
+ if not logger.handlers:
63
+ _install_default_handler()
64
+
65
+
66
+ _LEVEL_ALIASES = {'quiet': logging.WARNING}
67
+
68
+
69
+ def set_verbosity(level: int | str = 'info') -> None:
70
+ """Set how much the calculator reports about what it is doing.
71
+
72
+ Args:
73
+ level: ``'quiet'`` to drop the progress messages and keep only
74
+ warnings, or any standard logging level as a name (``'info'``,
75
+ ``'debug'``, ``'warning'``, ``'error'``) or as an integer. Pass
76
+ ``logging.CRITICAL`` to suppress the warnings as well.
77
+
78
+ Raises:
79
+ ValueError: If *level* is not a recognized level name.
80
+ """
81
+ if isinstance(level, str):
82
+ resolved = _LEVEL_ALIASES.get(level.lower(), logging.getLevelName(level.upper()))
83
+ if not isinstance(resolved, int):
84
+ raise ValueError(
85
+ f"Unknown verbosity '{level}'. Use 'quiet', 'info', 'debug', "
86
+ "'warning', 'error', or a logging level number."
87
+ )
88
+ level = resolved
89
+ logger.setLevel(level)
90
+
91
+
92
+ def _encodable(text: str) -> bool:
93
+ """Return True when the current stdout can encode *text*."""
94
+ encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
95
+ try:
96
+ text.encode(encoding)
97
+ except (LookupError, UnicodeEncodeError):
98
+ return False
99
+ return True
100
+
101
+
102
+ def _glyph(preferred: str, fallback: str) -> str:
103
+ return preferred if _encodable(preferred) else fallback
104
+
105
+
106
+ #: Prefix marking a completed action in a status message.
107
+ OK = _glyph('✓', '[ok]')
108
+ #: Unit of q.
109
+ INVERSE_ANGSTROM = _glyph('Å⁻¹', '1/Ang')
110
+ #: Unit of length.
111
+ ANGSTROM = _glyph('Å', 'Ang')
112
+ #: Unit of volume.
113
+ CUBIC_ANGSTROM = _glyph('ų', 'Ang^3')
114
+ #: Unit of scattering length density, as sasmodels parameters use it.
115
+ SLD_UNIT = _glyph('10⁻⁶ Å⁻²', '1e-6/Ang^2')