pytycho 0.2.0__tar.gz

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 (147) hide show
  1. pytycho-0.2.0/.claude-plugin/marketplace.json +18 -0
  2. pytycho-0.2.0/.claude-plugin/plugin.json +12 -0
  3. pytycho-0.2.0/.github/check_versions.py +46 -0
  4. pytycho-0.2.0/.github/dependabot.yml +42 -0
  5. pytycho-0.2.0/.github/release.yml +5 -0
  6. pytycho-0.2.0/.github/workflows/cd.yml +152 -0
  7. pytycho-0.2.0/.github/workflows/ci.yml +159 -0
  8. pytycho-0.2.0/.gitignore +37 -0
  9. pytycho-0.2.0/.pre-commit-config.yaml +84 -0
  10. pytycho-0.2.0/.readthedocs.yaml +17 -0
  11. pytycho-0.2.0/AGENTS.md +31 -0
  12. pytycho-0.2.0/AUTHORS +7 -0
  13. pytycho-0.2.0/CHANGELOG.md +31 -0
  14. pytycho-0.2.0/CONTRIBUTING.md +60 -0
  15. pytycho-0.2.0/LICENSE +202 -0
  16. pytycho-0.2.0/NOTICE +14 -0
  17. pytycho-0.2.0/PKG-INFO +161 -0
  18. pytycho-0.2.0/README.md +125 -0
  19. pytycho-0.2.0/SECURITY.md +5 -0
  20. pytycho-0.2.0/THIRD_PARTY_NOTICES.md +68 -0
  21. pytycho-0.2.0/docs/README.md +52 -0
  22. pytycho-0.2.0/docs/_static/tycho.css +6 -0
  23. pytycho-0.2.0/docs/agents.md +34 -0
  24. pytycho-0.2.0/docs/api/index.md +8 -0
  25. pytycho-0.2.0/docs/api/javascript.md +190 -0
  26. pytycho-0.2.0/docs/api/python.md +42 -0
  27. pytycho-0.2.0/docs/changelog.md +5 -0
  28. pytycho-0.2.0/docs/conf.py +181 -0
  29. pytycho-0.2.0/docs/conformance.md +4 -0
  30. pytycho-0.2.0/docs/gallery.md +42 -0
  31. pytycho-0.2.0/docs/guide/bodies.md +34 -0
  32. pytycho-0.2.0/docs/guide/clock.md +57 -0
  33. pytycho-0.2.0/docs/guide/frames.md +73 -0
  34. pytycho-0.2.0/docs/guide/html.md +49 -0
  35. pytycho-0.2.0/docs/guide/index.md +51 -0
  36. pytycho-0.2.0/docs/guide/panels.md +113 -0
  37. pytycho-0.2.0/docs/guide/thinning.md +26 -0
  38. pytycho-0.2.0/docs/guide/track-sets.md +85 -0
  39. pytycho-0.2.0/docs/guide/variables.md +39 -0
  40. pytycho-0.2.0/docs/guide/views.md +118 -0
  41. pytycho-0.2.0/docs/index.md +84 -0
  42. pytycho-0.2.0/docs/quickstart.md +95 -0
  43. pytycho-0.2.0/docs/requirements.txt +10 -0
  44. pytycho-0.2.0/docs/schema.md +15 -0
  45. pytycho-0.2.0/docs/spec.md +9 -0
  46. pytycho-0.2.0/examples/broucke/build.py +244 -0
  47. pytycho-0.2.0/hatch_build.py +19 -0
  48. pytycho-0.2.0/noxfile.py +68 -0
  49. pytycho-0.2.0/pyproject.toml +136 -0
  50. pytycho-0.2.0/skills/tycho/SKILL.md +78 -0
  51. pytycho-0.2.0/spec/conformance/README.md +65 -0
  52. pytycho-0.2.0/spec/conformance/broucke-mini/cases.json +16114 -0
  53. pytycho-0.2.0/spec/conformance/broucke-mini/data.tyd +0 -0
  54. pytycho-0.2.0/spec/conformance/broucke-mini/scene.tys +326 -0
  55. pytycho-0.2.0/spec/conformance/containers/encoded.tyd +0 -0
  56. pytycho-0.2.0/spec/conformance/containers/raw.tyd +0 -0
  57. pytycho-0.2.0/spec/conformance/edge-fraction/cases.json +195 -0
  58. pytycho-0.2.0/spec/conformance/edge-fraction/data.tyd +0 -0
  59. pytycho-0.2.0/spec/conformance/edge-fraction/rejections.json +446 -0
  60. pytycho-0.2.0/spec/conformance/edge-fraction/scene.tys +206 -0
  61. pytycho-0.2.0/spec/conformance/edge-value/cases.json +3428 -0
  62. pytycho-0.2.0/spec/conformance/edge-value/data.tyd +0 -0
  63. pytycho-0.2.0/spec/conformance/edge-value/scene.tys +199 -0
  64. pytycho-0.2.0/spec/conformance/generate.py +1283 -0
  65. pytycho-0.2.0/spec/conformance/laps/cases.json +44164 -0
  66. pytycho-0.2.0/spec/conformance/laps/data.tyd +0 -0
  67. pytycho-0.2.0/spec/conformance/laps/rejections.json +297 -0
  68. pytycho-0.2.0/spec/conformance/laps/scene.tys +251 -0
  69. pytycho-0.2.0/spec/conformance/rotating-loop/cases.json +8384 -0
  70. pytycho-0.2.0/spec/conformance/rotating-loop/data.tyd +0 -0
  71. pytycho-0.2.0/spec/conformance/rotating-loop/scene.tys +149 -0
  72. pytycho-0.2.0/spec/conformance/two-panels/cases.json +21588 -0
  73. pytycho-0.2.0/spec/conformance/two-panels/data.tyd +0 -0
  74. pytycho-0.2.0/spec/conformance/two-panels/scene.tys +257 -0
  75. pytycho-0.2.0/spec/conformance/value-clock/cases.json +3566 -0
  76. pytycho-0.2.0/spec/conformance/value-clock/data.tyd +0 -0
  77. pytycho-0.2.0/spec/conformance/value-clock/scene.tys +161 -0
  78. pytycho-0.2.0/spec/scene.schema.json +1414 -0
  79. pytycho-0.2.0/spec/tycho-scene.md +209 -0
  80. pytycho-0.2.0/src/pytycho/__init__.py +56 -0
  81. pytycho-0.2.0/src/pytycho/_evaluate.py +836 -0
  82. pytycho-0.2.0/src/pytycho/_static/tycho.js +4164 -0
  83. pytycho-0.2.0/src/pytycho/_thin.py +126 -0
  84. pytycho-0.2.0/src/pytycho/container.py +373 -0
  85. pytycho-0.2.0/src/pytycho/html.py +130 -0
  86. pytycho-0.2.0/src/pytycho/py.typed +0 -0
  87. pytycho-0.2.0/src/pytycho/scene.py +1581 -0
  88. pytycho-0.2.0/src/pytycho/template.html +16 -0
  89. pytycho-0.2.0/tests/test_conformance.py +91 -0
  90. pytycho-0.2.0/tests/test_container.py +318 -0
  91. pytycho-0.2.0/tests/test_evaluate.py +204 -0
  92. pytycho-0.2.0/tests/test_examples.py +67 -0
  93. pytycho-0.2.0/tests/test_scene.py +659 -0
  94. pytycho-0.2.0/tests/test_thin.py +54 -0
  95. pytycho-0.2.0/uv.lock +1533 -0
  96. pytycho-0.2.0/web/.prettierignore +3 -0
  97. pytycho-0.2.0/web/.prettierrc.json +4 -0
  98. pytycho-0.2.0/web/README.md +38 -0
  99. pytycho-0.2.0/web/dev/index.html +175 -0
  100. pytycho-0.2.0/web/package-lock.json +2339 -0
  101. pytycho-0.2.0/web/package.json +73 -0
  102. pytycho-0.2.0/web/scripts/frame-profile.mjs +338 -0
  103. pytycho-0.2.0/web/scripts/gen-types.mjs +36 -0
  104. pytycho-0.2.0/web/scripts/notices.mjs +61 -0
  105. pytycho-0.2.0/web/scripts/poster.mjs +91 -0
  106. pytycho-0.2.0/web/scripts/prepack.mjs +12 -0
  107. pytycho-0.2.0/web/src/container.ts +358 -0
  108. pytycho-0.2.0/web/src/errors.ts +18 -0
  109. pytycho-0.2.0/web/src/evaluate/clock.ts +94 -0
  110. pytycho-0.2.0/web/src/evaluate/frames.ts +261 -0
  111. pytycho-0.2.0/web/src/evaluate/index.ts +62 -0
  112. pytycho-0.2.0/web/src/evaluate/layers.ts +149 -0
  113. pytycho-0.2.0/web/src/evaluate/resolve.ts +154 -0
  114. pytycho-0.2.0/web/src/evaluate/sets.ts +158 -0
  115. pytycho-0.2.0/web/src/evaluate/types.ts +44 -0
  116. pytycho-0.2.0/web/src/index.ts +28 -0
  117. pytycho-0.2.0/web/src/runtime/colormaps.ts +44 -0
  118. pytycho-0.2.0/web/src/runtime/colors.ts +208 -0
  119. pytycho-0.2.0/web/src/runtime/element.ts +1985 -0
  120. pytycho-0.2.0/web/src/runtime/export.ts +439 -0
  121. pytycho-0.2.0/web/src/runtime/geometry.ts +627 -0
  122. pytycho-0.2.0/web/src/runtime/icons.ts +77 -0
  123. pytycho-0.2.0/web/src/runtime/layout.ts +80 -0
  124. pytycho-0.2.0/web/src/runtime/overlay.ts +45 -0
  125. pytycho-0.2.0/web/src/runtime/readouts.ts +351 -0
  126. pytycho-0.2.0/web/src/runtime/renderer.ts +1958 -0
  127. pytycho-0.2.0/web/src/runtime/state.ts +330 -0
  128. pytycho-0.2.0/web/src/runtime/tiles.ts +71 -0
  129. pytycho-0.2.0/web/src/scene.gen.ts +484 -0
  130. pytycho-0.2.0/web/src/scene.ts +47 -0
  131. pytycho-0.2.0/web/src/validate.ts +452 -0
  132. pytycho-0.2.0/web/test/chrome.test.ts +919 -0
  133. pytycho-0.2.0/web/test/conformance.test.ts +133 -0
  134. pytycho-0.2.0/web/test/container.test.ts +252 -0
  135. pytycho-0.2.0/web/test/corpus.ts +44 -0
  136. pytycho-0.2.0/web/test/dom-shim.ts +267 -0
  137. pytycho-0.2.0/web/test/evaluate.test.ts +234 -0
  138. pytycho-0.2.0/web/test/export.test.ts +364 -0
  139. pytycho-0.2.0/web/test/fit.test.ts +296 -0
  140. pytycho-0.2.0/web/test/geometry.test.ts +321 -0
  141. pytycho-0.2.0/web/test/helpers.ts +132 -0
  142. pytycho-0.2.0/web/test/runtime-helpers.test.ts +233 -0
  143. pytycho-0.2.0/web/test/scene-types.test.ts +305 -0
  144. pytycho-0.2.0/web/test/state.test.ts +191 -0
  145. pytycho-0.2.0/web/test/tsconfig.json +9 -0
  146. pytycho-0.2.0/web/tsconfig.build.json +11 -0
  147. pytycho-0.2.0/web/tsconfig.json +17 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "beeson-group",
3
+ "owner": {
4
+ "name": "Beeson Group"
5
+ },
6
+ "metadata": {
7
+ "description": "Agent skills for the Beeson Group's astrodynamics tools"
8
+ },
9
+ "plugins": [
10
+ {
11
+ "name": "tycho",
12
+ "source": "./",
13
+ "description": "Write TyCHO scenes with pytycho and render them as one self-contained HTML page",
14
+ "version": "0.2.0",
15
+ "category": "visualization"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "tycho",
3
+ "version": "0.2.0",
4
+ "description": "Write TyCHO scenes with pytycho from NumPy arrays and render them as one self-contained HTML page",
5
+ "author": {
6
+ "name": "Claudio Vestini"
7
+ },
8
+ "homepage": "https://pytycho.readthedocs.io/",
9
+ "repository": "https://github.com/beeson-group/tycho",
10
+ "license": "Apache-2.0",
11
+ "keywords": ["astrodynamics", "visualization", "three-body", "pytycho"]
12
+ }
@@ -0,0 +1,46 @@
1
+ """Fails unless pytycho, the npm package, and the Claude Code plugin carry the same version.
2
+
3
+ Given a tag such as ``v0.2.0``, it also fails unless the tag names this version and CHANGELOG.md has a
4
+ section for it. A release thus cannot go out with mismatched versions or without its notes. The script
5
+ uses only the standard library.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ ROOT = Path(__file__).resolve().parents[1]
16
+
17
+
18
+ def versions() -> dict[str, str]:
19
+ init = (ROOT / "src" / "pytycho" / "__init__.py").read_text(encoding="utf-8")
20
+ match = re.search(r'^__version__ = "([^"]+)"', init, re.MULTILINE)
21
+ marketplace = json.loads((ROOT / ".claude-plugin" / "marketplace.json").read_text(encoding="utf-8"))
22
+ return {
23
+ "src/pytycho/__init__.py": match.group(1) if match else "<missing>",
24
+ "web/package.json": json.loads((ROOT / "web" / "package.json").read_text(encoding="utf-8"))["version"],
25
+ ".claude-plugin/plugin.json": json.loads((ROOT / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8"))["version"],
26
+ ".claude-plugin/marketplace.json": next(p["version"] for p in marketplace["plugins"] if p["name"] == "tycho"),
27
+ }
28
+
29
+
30
+ def main(argv: list[str]) -> str | None:
31
+ found = versions()
32
+ if len(set(found.values())) != 1:
33
+ return "versions disagree: " + ", ".join(f"{path} {v}" for path, v in found.items())
34
+ version = next(iter(found.values()))
35
+ if argv:
36
+ tag = argv[0]
37
+ if tag != f"v{version}":
38
+ return f"tag {tag} does not name version {version}"
39
+ changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
40
+ if f"## [{version}]" not in changelog:
41
+ return f"CHANGELOG.md does not have a section ## [{version}]"
42
+ return None
43
+
44
+
45
+ if __name__ == "__main__":
46
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,42 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ cooldown:
8
+ default-days: 7
9
+ groups:
10
+ actions:
11
+ patterns:
12
+ - "*"
13
+ - package-ecosystem: "pre-commit"
14
+ directory: "/"
15
+ schedule:
16
+ interval: "monthly"
17
+ cooldown:
18
+ default-days: 7
19
+ groups:
20
+ pre-commit:
21
+ patterns:
22
+ - "*"
23
+ - package-ecosystem: "pip"
24
+ directory: "/docs"
25
+ schedule:
26
+ interval: "monthly"
27
+ cooldown:
28
+ default-days: 7
29
+ groups:
30
+ docs:
31
+ patterns:
32
+ - "*"
33
+ - package-ecosystem: "npm"
34
+ directory: "/web"
35
+ schedule:
36
+ interval: "monthly"
37
+ cooldown:
38
+ default-days: 7
39
+ groups:
40
+ npm:
41
+ patterns:
42
+ - "*"
@@ -0,0 +1,5 @@
1
+ changelog:
2
+ exclude:
3
+ authors:
4
+ - dependabot[bot]
5
+ - pre-commit-ci[bot]
@@ -0,0 +1,152 @@
1
+ name: CD
2
+
3
+ # On a published release, this workflow uploads pytycho to PyPI and @beeson-group/tycho to npm, both through
4
+ # trusted publishing with OpenID Connect, hence it does not store any token. The runtime is built once, thus
5
+ # both packages carry the same bundle, and neither is published unless both build and pass their tests. A
6
+ # manual run uploads pytycho to TestPyPI as a release candidate and does not publish to npm, which lacks a
7
+ # test registry. Trusted publishing to npm needs an existing package, and @beeson-group/tycho exists there.
8
+
9
+ on:
10
+ workflow_dispatch:
11
+ release:
12
+ types:
13
+ - published
14
+
15
+ permissions: {}
16
+
17
+ concurrency:
18
+ group: ${{ github.workflow }}-${{ github.ref }}
19
+ cancel-in-progress: false # never cancel a publish halfway
20
+
21
+ env:
22
+ FORCE_COLOR: 3
23
+
24
+ jobs:
25
+ runtime:
26
+ name: Build, test, and pack the runtime
27
+ runs-on: ubuntu-latest
28
+ permissions:
29
+ contents: read
30
+ steps:
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
+ with:
33
+ persist-credentials: false
34
+ - name: The versions agree and match the tag on a release
35
+ env:
36
+ TAG: ${{ github.event_name == 'release' && github.ref_name || '' }}
37
+ run: python3 .github/check_versions.py ${TAG:+"$TAG"}
38
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
39
+ with:
40
+ node-version: 24
41
+ package-manager-cache: false # a release workflow does not use a cache
42
+ - name: Test, then pack, which checks the notices and builds the bundles and the types
43
+ working-directory: web
44
+ run: npm ci && npm run typecheck && npm test && npm pack
45
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
46
+ with:
47
+ name: npm-package
48
+ path: web/beeson-group-tycho-*.tgz
49
+ if-no-files-found: error
50
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
51
+ with:
52
+ name: runtime
53
+ path: src/pytycho/_static/tycho.js
54
+ if-no-files-found: error
55
+
56
+ dist:
57
+ name: Build and check the distributions
58
+ needs: [runtime]
59
+ runs-on: ubuntu-latest
60
+ permissions:
61
+ contents: read
62
+ steps:
63
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
64
+ with:
65
+ persist-credentials: false
66
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
67
+ with:
68
+ name: runtime
69
+ path: src/pytycho/_static
70
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
71
+ with:
72
+ python-version: "3.14"
73
+ enable-cache: false # a release workflow does not use a cache
74
+ - run: uv run pytest
75
+ - run: uv build
76
+ - run: uvx twine check --strict dist/*
77
+ - name: The wheel carries the runtime and the notices
78
+ run: |
79
+ unzip -l dist/*.whl | tee wheel.txt
80
+ for f in pytycho/_static/tycho.js licenses/LICENSE licenses/NOTICE licenses/THIRD_PARTY_NOTICES.md; do
81
+ grep -q "$f" wheel.txt || { echo "::error::the wheel has no $f"; exit 1; }
82
+ done
83
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
84
+ with:
85
+ name: Packages
86
+ path: dist/*
87
+ if-no-files-found: error
88
+
89
+ publish-pypi:
90
+ name: Publish to PyPI
91
+ if: github.event_name == 'release' && github.event.action == 'published'
92
+ needs: [dist]
93
+ runs-on: ubuntu-latest
94
+ environment:
95
+ name: pypi
96
+ url: https://pypi.org/p/pytycho
97
+ permissions:
98
+ id-token: write # trusted publishing and the attestations need it
99
+ attestations: write # actions/attest writes the attestations
100
+ contents: read
101
+ steps:
102
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
103
+ with:
104
+ name: Packages
105
+ path: dist
106
+ - name: Generate artifact attestations for the sdist and the wheel
107
+ uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
108
+ with:
109
+ subject-path: "dist/*"
110
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
111
+
112
+ publish-testpypi:
113
+ name: Publish to TestPyPI
114
+ if: github.event_name == 'workflow_dispatch'
115
+ needs: [dist]
116
+ runs-on: ubuntu-latest
117
+ environment:
118
+ name: testpypi
119
+ url: https://test.pypi.org/p/pytycho
120
+ permissions:
121
+ id-token: write # trusted publishing needs it
122
+ contents: read
123
+ steps:
124
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
125
+ with:
126
+ name: Packages
127
+ path: dist
128
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
129
+ with:
130
+ repository-url: https://test.pypi.org/legacy/
131
+
132
+ publish-npm:
133
+ name: Publish to npm
134
+ if: github.event_name == 'release' && github.event.action == 'published'
135
+ needs: [runtime, dist]
136
+ runs-on: ubuntu-latest
137
+ environment:
138
+ name: npm
139
+ url: https://www.npmjs.com/package/@beeson-group/tycho
140
+ permissions:
141
+ contents: read
142
+ id-token: write # trusted publishing and provenance need it
143
+ steps:
144
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
145
+ with:
146
+ name: npm-package
147
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
148
+ with:
149
+ node-version: 24 # trusted publishing needs npm 11.5.1 or later, and Node 24.21 bundles npm 11.19
150
+ registry-url: https://registry.npmjs.org
151
+ package-manager-cache: false
152
+ - run: npm publish ./beeson-group-tycho-*.tgz --access public
@@ -0,0 +1,159 @@
1
+ name: CI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - main
9
+
10
+ permissions: {}
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ env:
17
+ FORCE_COLOR: 3
18
+
19
+ jobs:
20
+ lint:
21
+ name: pre-commit
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: read
25
+ steps:
26
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
+ with:
28
+ persist-credentials: false
29
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
30
+ - run: uvx pre-commit run --all-files --show-diff-on-failure
31
+
32
+ tests:
33
+ name: Python ${{ matrix.python-version }} on ${{ matrix.runs-on }}
34
+ runs-on: ${{ matrix.runs-on }}
35
+ permissions:
36
+ contents: read
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
41
+ runs-on: [ubuntu-latest]
42
+ include:
43
+ - python-version: "3.10"
44
+ runs-on: macos-latest
45
+ - python-version: "3.14"
46
+ runs-on: macos-latest
47
+ steps:
48
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
49
+ with:
50
+ persist-credentials: false
51
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
52
+ with:
53
+ python-version: ${{ matrix.python-version }}
54
+ - run: uv run pytest --durations=10
55
+
56
+ lowest:
57
+ name: Python 3.10 at the lowest direct dependencies
58
+ runs-on: ubuntu-latest
59
+ permissions:
60
+ contents: read
61
+ steps:
62
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
63
+ with:
64
+ persist-credentials: false
65
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
66
+ with:
67
+ python-version: "3.10"
68
+ - run: uv run --resolution lowest-direct pytest
69
+
70
+ web:
71
+ name: Runtime
72
+ runs-on: ubuntu-latest
73
+ permissions:
74
+ contents: read
75
+ defaults:
76
+ run:
77
+ working-directory: web
78
+ steps:
79
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
80
+ with:
81
+ persist-credentials: false
82
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
83
+ with:
84
+ node-version: 24
85
+ cache: npm
86
+ cache-dependency-path: web/package-lock.json
87
+ - run: npm ci
88
+ - run: npm run typecheck
89
+ - run: npm test
90
+ - name: Pack, which checks the notices and builds the bundles and the types
91
+ run: npm pack
92
+ - run: npx --yes publint@0.3 --strict
93
+ - run: npx --yes @arethetypeswrong/cli@0.18 ./beeson-group-tycho-*.tgz --profile esm-only --exclude-entrypoints ./iife
94
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
95
+ with:
96
+ name: runtime
97
+ path: src/pytycho/_static/tycho.js
98
+ if-no-files-found: error
99
+
100
+ dist:
101
+ name: Distributions
102
+ needs: [web]
103
+ runs-on: ubuntu-latest
104
+ permissions:
105
+ contents: read
106
+ steps:
107
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
108
+ with:
109
+ persist-credentials: false
110
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
111
+ with:
112
+ name: runtime
113
+ path: src/pytycho/_static
114
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
115
+ - run: python3 .github/check_versions.py
116
+ - run: uv build
117
+ - run: uvx twine check --strict dist/*
118
+ - name: The wheel carries the runtime and the notices, and the sdist does not carry example data
119
+ run: |
120
+ unzip -l dist/*.whl | tee wheel.txt
121
+ for f in pytycho/_static/tycho.js licenses/LICENSE licenses/NOTICE licenses/THIRD_PARTY_NOTICES.md; do
122
+ grep -q "$f" wheel.txt || { echo "::error::the wheel has no $f"; exit 1; }
123
+ done
124
+ if tar -tzf dist/*.tar.gz | grep -E '^[^/]+/examples/.*\.(csv|tys|tyd|html)$'; then
125
+ echo "::error::the sdist carries example data"; exit 1
126
+ fi
127
+ - name: The installed wheel writes a page that carries the runtime and its notices
128
+ run: |
129
+ uv venv /tmp/wheel-env
130
+ uv pip install --python /tmp/wheel-env/bin/python dist/*.whl
131
+ /tmp/wheel-env/bin/python - <<'EOF'
132
+ import numpy as np
133
+ import pytycho as ty
134
+
135
+ scene = ty.Scene(title="smoke")
136
+ frame = scene.frame("f", axes=("x", "y", "z"))
137
+ loop = scene.track_set("loop", frame=frame, periodic=True)
138
+ t = np.linspace(0.0, 2.0 * np.pi, 50)
139
+ loop.add(np.column_stack([np.cos(t), np.sin(t)]))
140
+ scene.clock(channels=["@index"], steps=50)
141
+ scene.view("main", frame=frame, driver=ty.driver(loop, 0), layers=[ty.track(loop)])
142
+ scene.panel("p", "main")
143
+ scene.write_html("smoke.html")
144
+ page = open("smoke.html", encoding="utf-8").read()
145
+ assert "This bundle contains" in page and "three 0." in page, "the page has no notice banner"
146
+ EOF
147
+
148
+ pass:
149
+ name: All checks pass
150
+ if: always()
151
+ needs: [lint, tests, lowest, web, dist]
152
+ runs-on: ubuntu-slim
153
+ timeout-minutes: 2
154
+ permissions: {}
155
+ steps:
156
+ - name: Decide whether the needed jobs succeeded or failed
157
+ uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0
158
+ with:
159
+ jobs: ${{ toJSON(needs) }}
@@ -0,0 +1,37 @@
1
+ # build outputs
2
+ web/node_modules/
3
+ web/dist/
4
+ src/pytycho/_static/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ __pycache__/
9
+ .pytest_cache/
10
+ .nox/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ docs/_build/
14
+ docs/_extra/
15
+
16
+ # copied into web/ by its prepack script, from the repository root
17
+ web/LICENSE
18
+ web/NOTICE
19
+ web/AUTHORS
20
+ web/THIRD_PARTY_NOTICES.md
21
+ web/*.tgz
22
+
23
+ # example inputs computed with PyDyLAN, which are not distributed
24
+ examples/**/data/
25
+
26
+ # generated by examples
27
+ examples/**/*.tyd
28
+ examples/**/*.tys
29
+ examples/**/*.html
30
+ examples/**/*.png
31
+
32
+ # local environment
33
+ .venv/
34
+ .DS_Store
35
+
36
+ # editor-local language-server settings
37
+ .zed/
@@ -0,0 +1,84 @@
1
+ # The lint job of .github/workflows/ci.yml runs these hooks, and Dependabot updates each rev monthly.
2
+ repos:
3
+ - repo: https://github.com/pre-commit/pre-commit-hooks
4
+ rev: "v6.0.0"
5
+ hooks:
6
+ - id: check-added-large-files
7
+ args: ["--maxkb=1024"] # the conformance corpus has cases of several hundred KB
8
+ - id: check-case-conflict
9
+ - id: check-merge-conflict
10
+ - id: check-symlinks
11
+ - id: check-yaml
12
+ - id: check-json
13
+ - id: check-toml
14
+ - id: debug-statements
15
+ - id: end-of-file-fixer
16
+ - id: mixed-line-ending
17
+ - id: name-tests-test
18
+ args: ["--pytest-test-first"]
19
+ - id: trailing-whitespace
20
+
21
+ - repo: https://github.com/pre-commit/pygrep-hooks
22
+ rev: "v1.10.0"
23
+ hooks:
24
+ - id: rst-backticks
25
+ - id: rst-directive-colons
26
+ - id: rst-inline-touching-normal
27
+
28
+ - repo: https://github.com/astral-sh/ruff-pre-commit
29
+ rev: "v0.16.10"
30
+ hooks:
31
+ - id: ruff-check
32
+ args: ["--fix", "--show-fixes"]
33
+ - id: ruff-format
34
+
35
+ # pyright is used because src passes its standard mode as written, while mypy would need code changes
36
+ - repo: https://github.com/RobertCraigie/pyright-python
37
+ rev: "v1.1.414"
38
+ hooks:
39
+ - id: pyright
40
+ files: ^(src|tests|examples)/
41
+ additional_dependencies: ["numpy", "pytest", "jsonschema"]
42
+
43
+ # prettier runs in check mode, since the TypeScript follows web/.prettierrc.json and is reformatted by hand
44
+ - repo: https://github.com/rbubley/mirrors-prettier
45
+ rev: "v3.9.9"
46
+ hooks:
47
+ - id: prettier
48
+ entry: prettier --check --ignore-unknown
49
+ files: ^web/(src|test|scripts)/
50
+ types_or: [ts, javascript]
51
+ exclude: ^web/src/scene\.gen\.ts$ # web/scripts/gen-types.mjs writes it, and a test compares it verbatim
52
+
53
+ - repo: https://github.com/codespell-project/codespell
54
+ rev: "v2.4.3"
55
+ hooks:
56
+ - id: codespell
57
+ additional_dependencies:
58
+ - tomli; python_version<'3.11'
59
+
60
+ - repo: local
61
+ hooks:
62
+ - id: disallow-caps
63
+ name: Disallow improper capitalization
64
+ language: pygrep
65
+ entry: PyBind|Numpy|Cmake|CCache|Github|PyTest|PyDylan|Pydylan|PyTycho
66
+ exclude: ^\.pre-commit-config\.yaml$
67
+
68
+ - repo: https://github.com/abravalheri/validate-pyproject
69
+ rev: "0.26"
70
+ hooks:
71
+ - id: validate-pyproject
72
+ additional_dependencies: ["validate-pyproject-schema-store[all]"]
73
+
74
+ - repo: https://github.com/python-jsonschema/check-jsonschema
75
+ rev: "0.38.2"
76
+ hooks:
77
+ - id: check-dependabot
78
+ - id: check-github-workflows
79
+ - id: check-readthedocs
80
+
81
+ - repo: https://github.com/zizmorcore/zizmor-pre-commit
82
+ rev: "v1.30.1"
83
+ hooks:
84
+ - id: zizmor
@@ -0,0 +1,17 @@
1
+ # Read the Docs serves the site at https://pytycho.readthedocs.io/. It builds the latest version on each push to
2
+ # main, the stable version from the highest release tag, and a preview for each pull request, linked from its checks.
3
+ # docs/conf.py puts src/ on the path, hence the build installs only the docs toolchain and NumPy, not the package.
4
+ version: 2
5
+
6
+ build:
7
+ os: ubuntu-24.04
8
+ tools:
9
+ python: "3.14"
10
+
11
+ sphinx:
12
+ configuration: docs/conf.py
13
+ fail_on_warning: true
14
+
15
+ python:
16
+ install:
17
+ - requirements: docs/requirements.txt
@@ -0,0 +1,31 @@
1
+ # AGENTS.md
2
+
3
+ TyCHO has two halves, which are released together at one version. pytycho (Python) writes a scene, i.e. the scene document `scene.tys` (JSON that specifies what to draw) and the data container `data.tyd` (the arrays), from NumPy arrays. It then inlines the browser runtime (TypeScript, `@beeson-group/tycho`) into one HTML page, which draws the scene.
4
+
5
+ ## Layout
6
+
7
+ - `src/pytycho/` holds the `Scene` builder (`scene.py`), the data container (`container.py`), the evaluator (`_evaluate.py`, which computes what each panel draws at a frame index), thinning (`_thin.py`, which keeps a subset of the stored samples), and the page writer (`html.py`, `template.html`). Its `_static/tycho.js` is the built runtime, which is gitignored and shipped in the wheel, hence it has to be built in a fresh checkout before pytycho can write a page.
8
+ - `web/src/` holds the runtime, i.e. the container and scene readers, the evaluator (`evaluate/`), and the `<tycho-viewer>` element with its renderer (`runtime/`). The file `scene.gen.ts` is generated from the schema by `npm run gen:types`. Never edit it by hand, since a test fails when it differs from the types generated from the schema.
9
+ - `spec/` holds the format rules, the JSON Schema, and the conformance corpus, i.e. scenes with the answers that both evaluators must give.
10
+ - `tests/` holds the pytest suite, and `web/test/` holds the vitest suite of the runtime.
11
+ - `examples/` holds the Broucke example, which builds its page from `broucke-input.tyd`, a file attached to each GitHub Release. The input and the outputs are gitignored.
12
+ - `docs/` holds the Sphinx site. `skills/tycho/` holds the agent skill for people who use pytycho, and `.claude-plugin/` holds its plugin manifests, which make the repository a Claude Code plugin marketplace.
13
+
14
+ ## Commands
15
+
16
+ ```sh
17
+ npm --prefix web ci && npm --prefix web run build # the runtime, into web/dist and src/pytycho/_static
18
+ npm --prefix web test && npm --prefix web run typecheck
19
+ uv run pytest
20
+ uvx pre-commit run --all-files # ruff, pyright, prettier --check, codespell, schema checks
21
+ uvx nox -s docs # the site, into docs/_build/html
22
+ uvx nox -s build # the sdist and the wheel, into dist/
23
+ python3 .github/check_versions.py # the four version fields agree
24
+ ```
25
+
26
+ ## Conventions
27
+
28
+ - Python lines run to 140 columns and are formatted by ruff. TypeScript lines run to 160 columns, use single quotes, and are checked by prettier, which only reports a misformatted file. Run `npx prettier --write <file>` from `web/` to apply the change.
29
+ - Where the Python and the TypeScript evaluators disagree, the answer in the corpus in `spec/` holds. Reword the rule to match it, add a case when the corpus does not cover the disagreement, and then fix both implementations.
30
+ - A version bump touches `src/pytycho/__init__.py`, `web/package.json`, and the two files in `.claude-plugin/` together, and `python3 .github/check_versions.py` fails while the four disagree.
31
+ - Every user-visible change gets a line under `## [Unreleased]` in `CHANGELOG.md`, since this section becomes the notes of the next release.
pytycho-0.2.0/AUTHORS ADDED
@@ -0,0 +1,7 @@
1
+ # The TyCHO Authors
2
+
3
+ The people who hold copyright in TyCHO, which the file NOTICE names together as "The TyCHO Authors".
4
+
5
+ - Claudio Vestini, Beeson Group, Princeton University
6
+ - Nina Karampela, Beeson Group, Princeton University
7
+ - Jack Sydney Schwartz, Beeson Group, Princeton University
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to TyCHO are recorded here, for both packages, `pytycho` on PyPI and `@beeson-group/tycho` on npm, which are released together at one version. The format follows [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/), and the versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Before 1.0, a breaking change, i.e. one after which existing code or files may stop working, bumps the minor version, and a fix bumps the patch version.
4
+
5
+ The two file formats are versioned apart from the packages, i.e. either format version may stay the same across a package release. The notes of each release therefore give the versions of the scene document `scene.tys` and the data container `data.tyd` that the packages read and write.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.0] - 2026-11-06
10
+
11
+ Version 0.2.0 introduces the scene grammar, i.e. a general vocabulary from which a visualization is declared. The packages at this version write and read scene format 0.2, which may still change incompatibly until 1.0, and data container version 1.
12
+
13
+ ### Added
14
+
15
+ - TyCHO describes each scene in a scene grammar. A scene is two files, which a Python script writes from NumPy arrays and the browser interprets. The grammar covers track sets (collections of trajectories) with per-sample channels (numbers stored at every sample, such as the time), bodies (named points), a tree of reference frames, variables, one clock, views with layers, panels, controls, and readouts. The rules of the grammar are numbered in `spec/tycho-scene.md`, and `spec/scene.schema.json` gives its structure. The conformance corpus in `spec/conformance/` holds scenes with the answers an evaluator must give, and both the Python and the TypeScript evaluators pass it.
16
+ - Two files store a scene. The scene document `scene.tys` is JSON that refers to the arrays by name and specifies what they are. The data container `data.tyd` holds a `TYCD` magic number, a JSON header, and named little-endian typed arrays. Each array is stored raw or, when this is smaller, compressed without loss in chunks of about 1 MiB. The compression takes the bit pattern of each number, replaces each row by its difference from the row before (delta coding), groups the bytes by their position within each number (byte shuffling), and deflates the result. A browser inflates, i.e. decompresses, these chunks with its native `DecompressionStream`, and a reader can decode one chunk at a time. The input of the Broucke example takes 60 percent of its raw size, and its page takes 12.7 MB, against 22.1 MB with the whole container gzipped.
17
+ - pytycho provides the `Scene` builder, thinning, and `evaluate`. The builder checks every call, then checks the whole scene for every value its variables can take, and reports each failure with the number of the rule it breaks. Thinning keeps a subset of the stored samples, selected by arclength, measured in the frames listed in the call, by default the frame of the set. The function `evaluate` returns what the browser draws for a frame index, so that a test can check what a scene draws without a browser.
18
+ - The runtime provides the `<tycho-viewer>` element, which draws any scene in panels that can be rearranged by drag and drop. Each panel keeps separate values of the panel-scoped variables, e.g. the frame that it shows. The element draws sliders, tabs, toggles, a transport (the controls that play and pause the clock and switch its active channel), and readouts.
19
+ - Playback runs at the refresh rate of the display. Every refresh draws at the progress given by the wall time, i.e. the real time elapsed, and this progress may lie between two frame indices. The layers under the first layer that takes a clock reading do not move with the animation. They are drawn once per panel, until the camera or these layers change, and copied back every frame. Body trails are read from positions kept per lap, so that a frame copies a trail instead of computing it. The frame rate shown counts the frames drawn in the last second. The script `web/scripts/frame-profile.mjs` measures what a frame of an example page costs, in headless Chrome.
20
+ - The viewer exports an MP4 video of its current state, from its controls or through `exportVideo`. The video is encoded in the browser itself, with the WebCodecs API and the mp4-muxer library.
21
+ - The npm package ships type declarations, which let TypeScript check code that uses the package.
22
+ - A track set stores its positions and channels as float32 by default. pytycho now stores a clock channel as float64 in a float32 set when float32 would round the ends of a step between samples by more than 1% of the step, or would make the channel nonmonotone. A nonmonotone channel would break rule T3, under which a clock channel increases or decreases strictly along every track. The argument `track_set(channel_dtypes=)` sets the stored dtype of a channel by hand.
23
+ - `build_html` embeds the container as base64 text without further compression, since its arrays are already compressed. It rejects a scene or data whose base64 is longer than 536,870,888 characters, the most that Chrome holds in one string, since a longer text would leave the page empty.
24
+ - Both container readers reject a view key or an encoding they do not know (rule D2), since a reader that ignored either could read the bytes wrongly. Section 1 of the spec specifies which changes bump the container version (rule D3). `Container.parse` in the runtime and the `load` method of the element return promises, since they inflate the encoded views, which the browser does asynchronously.
25
+ - The Broucke example reads its trajectories from `broucke-input.tyd`, a data container attached to each GitHub Release. It imports only NumPy, pytycho, and the standard library, hence it runs without PyDyLAN.
26
+ - An agent skill, `skills/tycho/SKILL.md`, sets out the scene grammar for a coding agent. It can be installed as a Claude Code plugin from this repository or with `npx skills add beeson-group/tycho`.
27
+ - The npm package bundles three.js and mp4-muxer, and it does not have any runtime dependencies, hence installing it does not install any other package.
28
+ - Both packages declare the license `Apache-2.0 AND MIT`, since the bundle carries MIT-licensed code, and they ship `NOTICE` and `THIRD_PARTY_NOTICES.md`. Every bundle starts with a comment listing the bundled packages. Every page that pytycho writes inlines the bundle, and thus carries the same comment.
29
+
30
+ [Unreleased]: https://github.com/beeson-group/tycho/compare/v0.2.0...HEAD
31
+ [0.2.0]: https://github.com/beeson-group/tycho/releases/tag/v0.2.0