scientific-computing-system 1.6.2__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.
Files changed (97) hide show
  1. cds/__init__.py +102 -0
  2. cds/__main__.py +8 -0
  3. cds/_version.py +10 -0
  4. cds/cli/__init__.py +104 -0
  5. cds/cli/__main__.py +8 -0
  6. cds/cli/_handlers.py +405 -0
  7. cds/cli/_parser.py +156 -0
  8. cds/cli/_style.py +117 -0
  9. cds/core/__init__.py +5 -0
  10. cds/core/_numeric.py +92 -0
  11. cds/core/models.py +166 -0
  12. cds/data_analysis/__init__.py +30 -0
  13. cds/data_analysis/dataset.py +122 -0
  14. cds/data_analysis/loader.py +75 -0
  15. cds/data_analysis/pandas_io.py +108 -0
  16. cds/data_analysis/transform.py +36 -0
  17. cds/data_analysis/viz.py +88 -0
  18. cds/diffeq/__init__.py +29 -0
  19. cds/diffeq/_implicit.py +349 -0
  20. cds/diffeq/solvers.py +312 -0
  21. cds/graph/__init__.py +31 -0
  22. cds/graph/algorithms.py +399 -0
  23. cds/hypothesis/__init__.py +48 -0
  24. cds/hypothesis/evaluator.py +369 -0
  25. cds/hypothesis/generator.py +275 -0
  26. cds/knowledge/__init__.py +41 -0
  27. cds/knowledge/graph.py +489 -0
  28. cds/knowledge/notes.py +231 -0
  29. cds/knowledge/retrieval.py +167 -0
  30. cds/math_utils/__init__.py +41 -0
  31. cds/math_utils/calculus.py +40 -0
  32. cds/math_utils/linalg.py +461 -0
  33. cds/math_utils/special.py +167 -0
  34. cds/ml/__init__.py +25 -0
  35. cds/ml/clustering.py +190 -0
  36. cds/ml/decomposition.py +172 -0
  37. cds/ml/linear_models.py +225 -0
  38. cds/ml/neighbors.py +162 -0
  39. cds/ml/neural.py +204 -0
  40. cds/ml/preprocessing.py +131 -0
  41. cds/ml/tree.py +202 -0
  42. cds/modeling/__init__.py +40 -0
  43. cds/modeling/_base.py +210 -0
  44. cds/modeling/_nodes.py +526 -0
  45. cds/modeling/expression.py +81 -0
  46. cds/modeling/model.py +134 -0
  47. cds/modeling/solver.py +176 -0
  48. cds/montecarlo/__init__.py +21 -0
  49. cds/montecarlo/methods.py +272 -0
  50. cds/nlp/__init__.py +170 -0
  51. cds/nlp/attention.py +285 -0
  52. cds/nlp/autograd/__init__.py +70 -0
  53. cds/nlp/autograd/_grad.py +120 -0
  54. cds/nlp/autograd/ops.py +133 -0
  55. cds/nlp/autograd/tensor.py +298 -0
  56. cds/nlp/bpe.py +449 -0
  57. cds/nlp/data.py +70 -0
  58. cds/nlp/embed.py +190 -0
  59. cds/nlp/layers.py +259 -0
  60. cds/nlp/model.py +456 -0
  61. cds/nlp/optim.py +163 -0
  62. cds/nlp/training.py +167 -0
  63. cds/nlp/viz.py +254 -0
  64. cds/numerical_integration/__init__.py +31 -0
  65. cds/numerical_integration/quadrature.py +480 -0
  66. cds/optimization/__init__.py +18 -0
  67. cds/optimization/_metaheuristics.py +287 -0
  68. cds/optimization/minimize.py +425 -0
  69. cds/plot/__init__.py +49 -0
  70. cds/plot/_backend.py +32 -0
  71. cds/plot/charts.py +462 -0
  72. cds/probability/__init__.py +55 -0
  73. cds/probability/_advanced.py +283 -0
  74. cds/probability/distributions.py +236 -0
  75. cds/py.typed +0 -0
  76. cds/quantum/__init__.py +51 -0
  77. cds/quantum/circuit.py +92 -0
  78. cds/quantum/multi_qubit.py +255 -0
  79. cds/quantum/simulator.py +38 -0
  80. cds/scientific/__init__.py +34 -0
  81. cds/scientific/constants.py +35 -0
  82. cds/scientific/formulas.py +116 -0
  83. cds/signals/__init__.py +43 -0
  84. cds/signals/filters.py +421 -0
  85. cds/signals/processing.py +224 -0
  86. cds/stats/__init__.py +83 -0
  87. cds/stats/_distributions.py +102 -0
  88. cds/stats/descriptive.py +172 -0
  89. cds/stats/hypothesis_tests.py +403 -0
  90. cds/stats/nonparametric.py +131 -0
  91. cds/stats/regression.py +51 -0
  92. cds/stats/time_series.py +408 -0
  93. scientific_computing_system-1.6.2.dist-info/METADATA +741 -0
  94. scientific_computing_system-1.6.2.dist-info/RECORD +97 -0
  95. scientific_computing_system-1.6.2.dist-info/WHEEL +4 -0
  96. scientific_computing_system-1.6.2.dist-info/entry_points.txt +2 -0
  97. scientific_computing_system-1.6.2.dist-info/licenses/LICENSE +21 -0
cds/knowledge/notes.py ADDED
@@ -0,0 +1,231 @@
1
+ """Research notes linked to concepts — :class:`Note` and :class:`Notebook`.
2
+
3
+ A :class:`Note` is a free-form record (title + body + tags) that can be
4
+ linked to one or more concept names in a :class:`KnowledgeGraph`. A
5
+ :class:`Notebook` is the collection that owns notes by id and answers
6
+ "which notes mention this concept?" and "which notes carry this tag?".
7
+
8
+ Like :mod:`cds.knowledge.graph`, persistence mirrors the
9
+ :class:`cds.nlp.bpe.BPEMerge` idiom: :meth:`to_dict`/``from_dict`` per
10
+ entity, :meth:`Notebook.save`/:meth:`Notebook.load` for file round-trips.
11
+
12
+ Notes are deliberately decoupled from the graph: a note may reference a
13
+ concept name that is not (yet) in any graph, and adding a concept never
14
+ requires touching notes. The link is a string, not a reference, mirroring
15
+ how a citation works in a lab notebook.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+
24
+
25
+ @dataclass
26
+ class Note:
27
+ """A single research note linked to zero or more concept names.
28
+
29
+ Attributes:
30
+ id: unique note identifier within a :class:`Notebook`.
31
+ title: short human-readable heading.
32
+ body: the note's free-form text content.
33
+ tags: grouping labels (e.g. ``["experiment", "failed"]``).
34
+ linked_concepts: names of concepts this note references. These are
35
+ plain strings, not references — they need not exist in any
36
+ particular :class:`KnowledgeGraph`.
37
+ created: optional ISO-8601 timestamp (or any caller-defined marker).
38
+ """
39
+
40
+ id: str
41
+ title: str
42
+ body: str
43
+ tags: list[str] = field(default_factory=list)
44
+ linked_concepts: list[str] = field(default_factory=list)
45
+ created: str | None = None
46
+
47
+ def to_dict(self) -> dict[str, object]:
48
+ """Serialize this note to a JSON-friendly dict."""
49
+ return {
50
+ "id": self.id,
51
+ "title": self.title,
52
+ "body": self.body,
53
+ "tags": list(self.tags),
54
+ "linked_concepts": list(self.linked_concepts),
55
+ "created": self.created,
56
+ }
57
+
58
+ @classmethod
59
+ def from_dict(cls, data: dict[str, object]) -> Note:
60
+ """Reconstruct a :class:`Note` from :meth:`to_dict` output.
61
+
62
+ Raises:
63
+ ValueError: if ``data`` is missing keys or has the wrong types.
64
+ """
65
+ note_id = data["id"]
66
+ title = data["title"]
67
+ body = data["body"]
68
+ if not isinstance(note_id, str):
69
+ raise ValueError(f"Invalid note id: {note_id!r}")
70
+ if not isinstance(title, str):
71
+ raise ValueError(f"Invalid note title: {title!r}")
72
+ if not isinstance(body, str):
73
+ raise ValueError(f"Invalid note body: {body!r}")
74
+ tags_raw = data["tags"]
75
+ if not isinstance(tags_raw, list) or not all(isinstance(t, str) for t in tags_raw):
76
+ raise ValueError(f"Invalid note tags: {tags_raw!r}")
77
+ linked_raw = data["linked_concepts"]
78
+ if not isinstance(linked_raw, list) or not all(isinstance(c, str) for c in linked_raw):
79
+ raise ValueError(f"Invalid note linked_concepts: {linked_raw!r}")
80
+ created = data["created"]
81
+ if created is not None and not isinstance(created, str):
82
+ raise ValueError(f"Invalid note created: {created!r}")
83
+ return cls(
84
+ id=note_id,
85
+ title=title,
86
+ body=body,
87
+ tags=list(tags_raw),
88
+ linked_concepts=list(linked_raw),
89
+ created=created,
90
+ )
91
+
92
+ def to_markdown(self) -> str:
93
+ """Render this note as a self-contained Markdown document."""
94
+ lines: list[str] = [f"# {self.title}", ""]
95
+ if self.created:
96
+ lines += [f"_Created: {self.created}_", ""]
97
+ if self.tags:
98
+ lines += ["Tags: " + ", ".join(f"`{tag}`" for tag in self.tags), ""]
99
+ lines += [self.body, ""]
100
+ if self.linked_concepts:
101
+ lines += ["## Linked concepts", ""]
102
+ for concept in self.linked_concepts:
103
+ lines.append(f"- `{concept}`")
104
+ lines.append("")
105
+ return "\n".join(lines)
106
+
107
+
108
+ @dataclass
109
+ class Notebook:
110
+ """An ordered collection of research notes keyed by id.
111
+
112
+ Attributes:
113
+ name: human-readable notebook title (used in :meth:`to_markdown`).
114
+ notes: mapping of note id to :class:`Note`.
115
+ """
116
+
117
+ name: str
118
+ notes: dict[str, Note] = field(default_factory=dict)
119
+
120
+ # ------------------------------------------------------------------ #
121
+ # Construction & lookup
122
+ # ------------------------------------------------------------------ #
123
+ def add_note(
124
+ self,
125
+ note_id: str,
126
+ title: str,
127
+ body: str,
128
+ tags: list[str] | None = None,
129
+ linked_concepts: list[str] | None = None,
130
+ created: str | None = None,
131
+ ) -> Note:
132
+ """Add a note, returning the stored :class:`Note`.
133
+
134
+ If ``note_id`` already exists it is overwritten (last-write-wins),
135
+ matching how a researcher edits a numbered entry in place.
136
+
137
+ Returns:
138
+ the stored :class:`Note`.
139
+ """
140
+ note = Note(
141
+ id=note_id,
142
+ title=title,
143
+ body=body,
144
+ tags=list(tags) if tags else [],
145
+ linked_concepts=list(linked_concepts) if linked_concepts else [],
146
+ created=created,
147
+ )
148
+ self.notes[note_id] = note
149
+ return note
150
+
151
+ def get_note(self, note_id: str) -> Note:
152
+ """Return the note with ``note_id``.
153
+
154
+ Raises:
155
+ KeyError: if ``note_id`` is not in this notebook.
156
+ """
157
+ if note_id not in self.notes:
158
+ raise KeyError(f"unknown note: {note_id!r}")
159
+ return self.notes[note_id]
160
+
161
+ def notes_for_concept(self, concept: str) -> list[Note]:
162
+ """All notes that reference ``concept`` (by linked_concepts membership)."""
163
+ return [note for note in self.notes.values() if concept in note.linked_concepts]
164
+
165
+ def notes_by_tag(self, tag: str) -> list[Note]:
166
+ """All notes carrying ``tag``."""
167
+ return [note for note in self.notes.values() if tag in note.tags]
168
+
169
+ # ------------------------------------------------------------------ #
170
+ # Rendering & serialization
171
+ # ------------------------------------------------------------------ #
172
+ def to_markdown(self) -> str:
173
+ """Render a compact index of this notebook's notes as Markdown."""
174
+ lines: list[str] = [f"# Notebook: {self.name}", ""]
175
+ if not self.notes:
176
+ lines += ["_No notes._", ""]
177
+ else:
178
+ lines += ["## Notes", ""]
179
+ for note_id in sorted(self.notes):
180
+ note = self.notes[note_id]
181
+ tags = f" ({', '.join(note.tags)})" if note.tags else ""
182
+ lines.append(f"- **{note_id}**: {note.title}{tags}")
183
+ lines.append("")
184
+ return "\n".join(lines)
185
+
186
+ def to_dict(self) -> dict[str, object]:
187
+ """Serialize the notebook to a JSON-friendly dict."""
188
+ return {
189
+ "name": self.name,
190
+ "notes": [note.to_dict() for note in self.notes.values()],
191
+ }
192
+
193
+ @classmethod
194
+ def from_dict(cls, data: dict[str, object]) -> Notebook:
195
+ """Reconstruct a :class:`Notebook` from :meth:`to_dict` output.
196
+
197
+ Raises:
198
+ ValueError: if ``data`` is missing keys or has the wrong types.
199
+ """
200
+ name = data["name"]
201
+ if not isinstance(name, str):
202
+ raise ValueError(f"Invalid notebook name: {name!r}")
203
+ notes_raw = data["notes"]
204
+ if not isinstance(notes_raw, list):
205
+ raise ValueError(f"Invalid notes list: {notes_raw!r}")
206
+ notebook = cls(name=name)
207
+ for item in notes_raw:
208
+ if not isinstance(item, dict):
209
+ raise ValueError(f"Invalid note entry: {item!r}")
210
+ note = Note.from_dict(item)
211
+ notebook.notes[note.id] = note
212
+ return notebook
213
+
214
+ def save(self, path: str | Path) -> None:
215
+ """Write this notebook to ``path`` as indented UTF-8 JSON."""
216
+ Path(path).write_text(
217
+ json.dumps(self.to_dict(), ensure_ascii=False, indent=2),
218
+ encoding="utf-8",
219
+ )
220
+
221
+ @classmethod
222
+ def load(cls, path: str | Path) -> Notebook:
223
+ """Read a notebook previously written by :meth:`save`.
224
+
225
+ Raises:
226
+ ValueError: if the file does not contain valid notebook JSON.
227
+ """
228
+ data = json.loads(Path(path).read_text(encoding="utf-8"))
229
+ if not isinstance(data, dict):
230
+ raise ValueError(f"Invalid notebook file (expected object): {data!r}")
231
+ return cls.from_dict(data)
@@ -0,0 +1,167 @@
1
+ """Structured retrieval across a knowledge graph and its notes.
2
+
3
+ Given a free-text ``query`` and an optional ``tag`` filter, :func:`search`
4
+ ranks matching concepts and notes by relevance. The scoring is deliberately
5
+ simple and deterministic — no statistics, no tokenization beyond case-folding
6
+ — so results are easy to explain and test:
7
+
8
+ * An exact name/tag match scores ``1.0`` (highest confidence).
9
+ * A substring match in the description/body/title scores ``0.5``.
10
+ * Results are sorted by descending score, then by name/id alphabetically
11
+ so ties resolve deterministically.
12
+
13
+ This is the *retrieval* half of Issue #3's four goals; the graph, concept
14
+ mapping, and note-management halves live in :mod:`cds.knowledge.graph` and
15
+ :mod:`cds.knowledge.notes`.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from dataclasses import dataclass
21
+
22
+ from cds.knowledge.graph import KnowledgeGraph
23
+ from cds.knowledge.notes import Notebook
24
+
25
+ # Score constants — exposed as module attributes for clarity in assertions.
26
+ NAME_TAG_SCORE = 1.0
27
+ SUBSTRING_SCORE = 0.5
28
+
29
+
30
+ @dataclass
31
+ class SearchResult:
32
+ """A single ranked retrieval hit.
33
+
34
+ Attributes:
35
+ concept_name: the matched concept name, if the hit is a concept;
36
+ ``None`` otherwise.
37
+ note_id: the matched note id, if the hit is a note; ``None`` otherwise.
38
+ score: relevance in ``[0, 1]`` — higher is better.
39
+ matched_on: short label of the field that matched
40
+ (e.g. ``"name"``, ``"description"``, ``"title"``).
41
+ """
42
+
43
+ concept_name: str | None
44
+ note_id: str | None
45
+ score: float
46
+ matched_on: str
47
+
48
+
49
+ def search_concepts(
50
+ graph: KnowledgeGraph,
51
+ query: str,
52
+ tag: str | None = None,
53
+ ) -> list[SearchResult]:
54
+ """Find concepts in ``graph`` matching ``query``.
55
+
56
+ A concept matches if its name matches the query exactly (score 1.0) or
57
+ its name or description contains the query as a substring (score 0.5).
58
+ When ``tag`` is given, only concepts carrying that tag are considered.
59
+
60
+ Args:
61
+ graph: the :class:`KnowledgeGraph` to search.
62
+ query: case-insensitive search text.
63
+ tag: optional tag filter; ``None`` disables filtering.
64
+
65
+ Returns:
66
+ ranked :class:`SearchResult` list (best first, ties alphabetical).
67
+ """
68
+ needle = query.casefold()
69
+ results: list[SearchResult] = []
70
+ for name in sorted(graph.concepts):
71
+ concept = graph.concepts[name]
72
+ if tag is not None and tag not in concept.tags:
73
+ continue
74
+ name_folded = name.casefold()
75
+ if name_folded == needle:
76
+ results.append(
77
+ SearchResult(
78
+ concept_name=name, note_id=None, score=NAME_TAG_SCORE, matched_on="name"
79
+ )
80
+ )
81
+ elif needle in name_folded:
82
+ results.append(
83
+ SearchResult(
84
+ concept_name=name, note_id=None, score=SUBSTRING_SCORE, matched_on="name"
85
+ )
86
+ )
87
+ elif concept.description is not None and needle in concept.description.casefold():
88
+ results.append(
89
+ SearchResult(
90
+ concept_name=name, note_id=None, score=SUBSTRING_SCORE, matched_on="description"
91
+ )
92
+ )
93
+ results.sort(key=lambda r: (-r.score, r.concept_name or ""))
94
+ return results
95
+
96
+
97
+ def search_notes(
98
+ notebook: Notebook,
99
+ query: str,
100
+ tag: str | None = None,
101
+ ) -> list[SearchResult]:
102
+ """Find notes in ``notebook`` matching ``query``.
103
+
104
+ A note matches if its title matches exactly (score 1.0) or its title or
105
+ body contains the query as a substring (score 0.5). When ``tag`` is
106
+ given, only notes carrying that tag are considered.
107
+
108
+ Args:
109
+ notebook: the :class:`Notebook` to search.
110
+ query: case-insensitive search text.
111
+ tag: optional tag filter; ``None`` disables filtering.
112
+
113
+ Returns:
114
+ ranked :class:`SearchResult` list (best first, ties alphabetical).
115
+ """
116
+ needle = query.casefold()
117
+ results: list[SearchResult] = []
118
+ for note_id in sorted(notebook.notes):
119
+ note = notebook.notes[note_id]
120
+ if tag is not None and tag not in note.tags:
121
+ continue
122
+ title_folded = note.title.casefold()
123
+ if title_folded == needle:
124
+ results.append(
125
+ SearchResult(
126
+ concept_name=None, note_id=note_id, score=NAME_TAG_SCORE, matched_on="title"
127
+ )
128
+ )
129
+ elif needle in title_folded:
130
+ results.append(
131
+ SearchResult(
132
+ concept_name=None, note_id=note_id, score=SUBSTRING_SCORE, matched_on="title"
133
+ )
134
+ )
135
+ elif needle in note.body.casefold():
136
+ results.append(
137
+ SearchResult(
138
+ concept_name=None, note_id=note_id, score=SUBSTRING_SCORE, matched_on="body"
139
+ )
140
+ )
141
+ results.sort(key=lambda r: (-r.score, r.note_id or ""))
142
+ return results
143
+
144
+
145
+ def search(
146
+ graph: KnowledgeGraph,
147
+ notebook: Notebook,
148
+ query: str,
149
+ tag: str | None = None,
150
+ ) -> list[SearchResult]:
151
+ """Combined ranked search over both a graph's concepts and a notebook's notes.
152
+
153
+ Results from :func:`search_concepts` and :func:`search_notes` are merged
154
+ and re-ranked by score (desc) then by identifier (asc).
155
+
156
+ Args:
157
+ graph: the :class:`KnowledgeGraph` whose concepts to search.
158
+ notebook: the :class:`Notebook` whose notes to search.
159
+ query: case-insensitive search text.
160
+ tag: optional tag filter applied to both concepts and notes.
161
+
162
+ Returns:
163
+ ranked :class:`SearchResult` list (best first, ties alphabetical).
164
+ """
165
+ combined = search_concepts(graph, query, tag) + search_notes(notebook, query, tag)
166
+ combined.sort(key=lambda r: (-r.score, r.concept_name or r.note_id or ""))
167
+ return combined
@@ -0,0 +1,41 @@
1
+ """Math helper functions."""
2
+
3
+ from cds.math_utils.calculus import derivative, gradient, integral
4
+ from cds.math_utils.linalg import (
5
+ cholesky,
6
+ determinant,
7
+ dot,
8
+ frobenius_norm,
9
+ gram_schmidt,
10
+ identity,
11
+ lu_decomposition,
12
+ mat_mul,
13
+ matrix_inverse,
14
+ matrix_trace,
15
+ power_iteration,
16
+ qr_decomposition,
17
+ solve_linear,
18
+ transpose,
19
+ vector_norm,
20
+ )
21
+
22
+ __all__ = [
23
+ "derivative",
24
+ "integral",
25
+ "gradient",
26
+ "dot",
27
+ "mat_mul",
28
+ "transpose",
29
+ "determinant",
30
+ "identity",
31
+ "lu_decomposition",
32
+ "solve_linear",
33
+ "matrix_inverse",
34
+ "power_iteration",
35
+ "gram_schmidt",
36
+ "qr_decomposition",
37
+ "cholesky",
38
+ "vector_norm",
39
+ "frobenius_norm",
40
+ "matrix_trace",
41
+ ]
@@ -0,0 +1,40 @@
1
+ """Numerical calculus — derivatives, integrals, gradients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+
7
+
8
+ def derivative(f: Callable[[float], float], x: float, h_base: float = 1e-7) -> float:
9
+ """Central difference approximation with adaptive step size."""
10
+ h = h_base * max(1.0, abs(x))
11
+ return (f(x + h) - f(x - h)) / (2 * h)
12
+
13
+
14
+ def integral(f: Callable[[float], float], a: float, b: float, n: int = 1000) -> float:
15
+ """Simpson's rule for numerical integration."""
16
+ if n % 2 != 0:
17
+ n += 1
18
+ h = (b - a) / n
19
+ s = f(a) + f(b)
20
+ for i in range(1, n):
21
+ coeff = 4 if i % 2 != 0 else 2
22
+ s += coeff * f(a + i * h)
23
+ return s * h / 3
24
+
25
+
26
+ def gradient(f: Callable[..., float], point: list[float], h_base: float = 1e-7) -> list[float]:
27
+ """Numerical gradient for multivariable functions with adaptive scaling."""
28
+ grad = []
29
+ for i in range(len(point)):
30
+ # Scale step size h relative to point magnitude to maintain precision
31
+ h = h_base * max(1.0, abs(point[i]))
32
+
33
+ def partial(val: float, idx: int = i) -> float:
34
+ """Return f evaluated with coordinate `idx` set to `val`."""
35
+ p = point.copy()
36
+ p[idx] = val
37
+ return f(*p)
38
+
39
+ grad.append((partial(point[i] + h) - partial(point[i] - h)) / (2 * h))
40
+ return grad