cct-cli 0.7.9.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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
calc_terminal/graphs.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graphing for Chemistry Calc Terminal.
|
|
3
|
+
|
|
4
|
+
A real terminal can't natively draw a rotatable 3D plot, so this module
|
|
5
|
+
takes the honest, practical two-track approach:
|
|
6
|
+
|
|
7
|
+
* ascii_plot() -- live, animated (pen draws left-to-right) 2D
|
|
8
|
+
character-grid plot rendered directly in the
|
|
9
|
+
terminal. No dependencies.
|
|
10
|
+
|
|
11
|
+
* export_2d/3d() -- high quality PNG rendered with matplotlib
|
|
12
|
+
(Agg backend, no display needed) for anyone
|
|
13
|
+
who wants a crisp 2D curve or a real rotatable-
|
|
14
|
+
looking 3D surface, saved to disk and opened
|
|
15
|
+
with the OS default viewer.
|
|
16
|
+
|
|
17
|
+
Preset curves cover the numericals already in the notebook engine so a
|
|
18
|
+
graph is one keystroke away from any solved topic.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import math
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
from . import theme
|
|
28
|
+
|
|
29
|
+
EXPORT_DIR = os.path.join(os.path.expanduser("~"), "cct_exports")
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------- custom / AI --
|
|
32
|
+
# The AI agent (and anyone typing a formula) can plot or export ANY named
|
|
33
|
+
# 2D function or 3D surface, not just the 10 built-in presets. This uses
|
|
34
|
+
# the same restricted-character + restricted-namespace `eval` approach as
|
|
35
|
+
# the quick calculator and the symbolic solver's constant table, so no
|
|
36
|
+
# arbitrary code execution is possible.
|
|
37
|
+
_MATH_NS = {
|
|
38
|
+
"sin": math.sin, "cos": math.cos, "tan": math.tan, "asin": math.asin,
|
|
39
|
+
"acos": math.acos, "atan": math.atan, "exp": math.exp, "log": math.log,
|
|
40
|
+
"log10": math.log10, "sqrt": math.sqrt, "pi": math.pi, "e": math.e,
|
|
41
|
+
"abs": abs, "pow": pow,
|
|
42
|
+
}
|
|
43
|
+
_EXPR_RE = re.compile(r"^[0-9A-Za-z_+\-*/().,\s]*$")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ExpressionError(Exception):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _compile_expr(expr_str, varnames):
|
|
51
|
+
"""Compile a user/AI-supplied math expression into a callable f(*vars).
|
|
52
|
+
Only arithmetic, the functions in _MATH_NS, and the given variable
|
|
53
|
+
names are reachable — no builtins, no attribute access."""
|
|
54
|
+
if not expr_str or not _EXPR_RE.match(expr_str.replace("^", "")):
|
|
55
|
+
raise ExpressionError(f"Invalid or unsafe expression: {expr_str!r}")
|
|
56
|
+
code = compile(expr_str.replace("^", "**"), "<expr>", "eval")
|
|
57
|
+
|
|
58
|
+
def f(*args):
|
|
59
|
+
ns = dict(_MATH_NS)
|
|
60
|
+
ns.update(dict(zip(varnames, args)))
|
|
61
|
+
return eval(code, {"__builtins__": {}}, ns)
|
|
62
|
+
|
|
63
|
+
# fail fast on a syntactically-valid-looking but broken expression
|
|
64
|
+
try:
|
|
65
|
+
f(*([1.0] * len(varnames)))
|
|
66
|
+
except ExpressionError:
|
|
67
|
+
raise
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
raise ExpressionError(f"Could not evaluate expression: {exc}")
|
|
70
|
+
return f
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def custom_curve(expr, xmin=0.0, xmax=10.0, points=60, title=None,
|
|
74
|
+
xlabel="x", ylabel="y", var="x"):
|
|
75
|
+
"""Build (xs, ys, title, xlabel, ylabel) for ANY named function of one
|
|
76
|
+
variable, e.g. custom_curve('sin(x)*exp(-x/5)', 0, 20, title='Damped wave').
|
|
77
|
+
This is what lets the AI agent — or a person — graph something that
|
|
78
|
+
isn't one of the 10 built-in chemistry presets."""
|
|
79
|
+
xmin, xmax = float(xmin), float(xmax)
|
|
80
|
+
if xmax <= xmin:
|
|
81
|
+
xmax = xmin + 1.0
|
|
82
|
+
f = _compile_expr(expr, [var])
|
|
83
|
+
xs = [xmin + i * (xmax - xmin) / points for i in range(points + 1)]
|
|
84
|
+
ys = []
|
|
85
|
+
for x in xs:
|
|
86
|
+
try:
|
|
87
|
+
val = float(f(x))
|
|
88
|
+
except Exception:
|
|
89
|
+
val = 0.0
|
|
90
|
+
ys.append(val if math.isfinite(val) else 0.0)
|
|
91
|
+
return xs, ys, title or f"{ylabel} = {expr}", xlabel, ylabel
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def export_custom_2d(expr, xmin=0.0, xmax=10.0, points=300, title=None,
|
|
95
|
+
xlabel="x", ylabel="y", filename=None):
|
|
96
|
+
xs, ys, t, xl, yl = custom_curve(expr, xmin, xmax, points, title, xlabel, ylabel)
|
|
97
|
+
return export_2d(xs, ys, t, xl, yl, filename)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def export_custom_3d(expr, xmin=-5.0, xmax=5.0, ymin=-5.0, ymax=5.0,
|
|
101
|
+
points=45, title=None, filename=None):
|
|
102
|
+
"""Export ANY named 3D surface z = f(x, y) as a high-quality PNG —
|
|
103
|
+
the free-form counterpart to export_3d_surface()'s two fixed presets."""
|
|
104
|
+
import numpy as np
|
|
105
|
+
import matplotlib
|
|
106
|
+
matplotlib.use("Agg")
|
|
107
|
+
import matplotlib.pyplot as plt
|
|
108
|
+
from mpl_toolkits.mplot3d import Axes3D # noqa: F401
|
|
109
|
+
_ensure_dir()
|
|
110
|
+
|
|
111
|
+
f = _compile_expr(expr, ["x", "y"])
|
|
112
|
+
xs = np.linspace(float(xmin), float(xmax), points)
|
|
113
|
+
ys = np.linspace(float(ymin), float(ymax), points)
|
|
114
|
+
X, Y = np.meshgrid(xs, ys)
|
|
115
|
+
Z = np.zeros_like(X)
|
|
116
|
+
for i in range(X.shape[0]):
|
|
117
|
+
for j in range(X.shape[1]):
|
|
118
|
+
try:
|
|
119
|
+
v = float(f(X[i, j], Y[i, j]))
|
|
120
|
+
Z[i, j] = v if math.isfinite(v) else 0.0
|
|
121
|
+
except Exception:
|
|
122
|
+
Z[i, j] = 0.0
|
|
123
|
+
|
|
124
|
+
fig = plt.figure(figsize=(7, 6), facecolor="#1a1b26")
|
|
125
|
+
ax = fig.add_subplot(111, projection="3d")
|
|
126
|
+
ax.set_facecolor("#1a1b26")
|
|
127
|
+
ax.plot_surface(X, Y, Z, cmap="viridis", edgecolor="none")
|
|
128
|
+
title = title or f"z = {expr}"
|
|
129
|
+
ax.set_title(title, color="#c0caf5")
|
|
130
|
+
ax.set_xlabel("x", color="#a9b1d6")
|
|
131
|
+
ax.set_ylabel("y", color="#a9b1d6")
|
|
132
|
+
ax.set_zlabel("z", color="#a9b1d6")
|
|
133
|
+
ax.tick_params(colors="#a9b1d6")
|
|
134
|
+
|
|
135
|
+
safe_name = re.sub(r"[^a-zA-Z0-9]+", "_", title.lower()).strip("_")[:40]
|
|
136
|
+
filename = filename or f"{safe_name or 'custom_surface'}.png"
|
|
137
|
+
path = os.path.join(EXPORT_DIR, filename)
|
|
138
|
+
fig.tight_layout()
|
|
139
|
+
fig.savefig(path, dpi=150)
|
|
140
|
+
plt.close(fig)
|
|
141
|
+
return path
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# --------------------------------------------------------------- presets --
|
|
145
|
+
def preset_curve(name, **params):
|
|
146
|
+
"""Returns (x_list, y_list, title, xlabel, ylabel) for a named
|
|
147
|
+
chemistry curve, computed from the real governing formula."""
|
|
148
|
+
n = 60
|
|
149
|
+
if name == "zero_order":
|
|
150
|
+
R0 = params.get("R0", 1.0); k = params.get("k", 0.02); tmax = params.get("tmax", 40)
|
|
151
|
+
xs = [i * tmax / n for i in range(n + 1)]
|
|
152
|
+
ys = [max(0.0, R0 - k * t) for t in xs]
|
|
153
|
+
return xs, ys, "Zero Order: [R] vs t", "time", "[R]"
|
|
154
|
+
if name == "first_order":
|
|
155
|
+
R0 = params.get("R0", 1.0); k = params.get("k", 0.05); tmax = params.get("tmax", 60)
|
|
156
|
+
xs = [i * tmax / n for i in range(n + 1)]
|
|
157
|
+
ys = [R0 * math.exp(-k * t) for t in xs]
|
|
158
|
+
return xs, ys, "First Order: [R] vs t", "time", "[R]"
|
|
159
|
+
if name == "first_order_log":
|
|
160
|
+
R0 = params.get("R0", 1.0); k = params.get("k", 0.05); tmax = params.get("tmax", 60)
|
|
161
|
+
xs = [i * tmax / n for i in range(n + 1)]
|
|
162
|
+
ys = [math.log10(max(R0 * math.exp(-k * t), 1e-12)) for t in xs]
|
|
163
|
+
return xs, ys, "First Order: log[R] vs t (straight line)", "time", "log[R]"
|
|
164
|
+
if name == "second_order":
|
|
165
|
+
R0 = params.get("R0", 1.0); k = params.get("k", 0.05); tmax = params.get("tmax", 60)
|
|
166
|
+
xs = [i * tmax / n for i in range(n + 1)]
|
|
167
|
+
ys = [1.0 / (1.0 / R0 + k * t) for t in xs]
|
|
168
|
+
return xs, ys, "Second Order: [R] vs t", "time", "[R]"
|
|
169
|
+
if name == "arrhenius":
|
|
170
|
+
A = params.get("A", 1e13); Ea = params.get("Ea", 75000)
|
|
171
|
+
Ts = [i for i in range(250, 251 + n * 4, 4)]
|
|
172
|
+
xs = [1.0 / T for T in Ts]
|
|
173
|
+
ys = [math.log(A * math.exp(-Ea / (8.314 * T))) for T in Ts]
|
|
174
|
+
return xs, ys, "Arrhenius: ln k vs 1/T", "1/T", "ln k"
|
|
175
|
+
if name == "boyles":
|
|
176
|
+
P1 = params.get("P1", 1.0); V1 = params.get("V1", 22.4)
|
|
177
|
+
xs = [0.5 + i * (10 - 0.5) / n for i in range(n + 1)]
|
|
178
|
+
ys = [(P1 * V1) / P for P in xs]
|
|
179
|
+
return xs, ys, "Boyle's Law: V vs P (T const.)", "P", "V"
|
|
180
|
+
if name == "maxwell_boltzmann":
|
|
181
|
+
T = params.get("T", 300); M = params.get("M", 0.028)
|
|
182
|
+
R = 8.314
|
|
183
|
+
xs = [i * 2500 / n for i in range(1, n + 1)]
|
|
184
|
+
ys = [4 * math.pi * (M / (2 * math.pi * R * T)) ** 1.5 * v * v *
|
|
185
|
+
math.exp(-M * v * v / (2 * R * T)) for v in xs]
|
|
186
|
+
return xs, ys, f"Maxwell-Boltzmann speed distribution (T={T}K)", "speed (m/s)", "f(v)"
|
|
187
|
+
if name == "radial_prob_1s":
|
|
188
|
+
xs = [i * 6 / n for i in range(n + 1)]
|
|
189
|
+
ys = [(r * r) * math.exp(-2 * r) for r in xs]
|
|
190
|
+
return xs, ys, "1s Radial Probability Density", "r (a0)", "r\u00b2R\u00b2"
|
|
191
|
+
if name == "titration":
|
|
192
|
+
Ca = params.get("Ca", 0.1); Va = params.get("Va", 25.0); Cb = params.get("Cb", 0.1)
|
|
193
|
+
xs = [i * 60 / n for i in range(1, n)]
|
|
194
|
+
ys = []
|
|
195
|
+
for Vb in xs:
|
|
196
|
+
moles_a = Ca * Va; moles_b = Cb * Vb
|
|
197
|
+
excess = moles_b - moles_a
|
|
198
|
+
total_v = Va + Vb
|
|
199
|
+
if excess > 0.0001:
|
|
200
|
+
pOH = -math.log10(excess / total_v)
|
|
201
|
+
ys.append(14 - pOH)
|
|
202
|
+
elif excess < -0.0001:
|
|
203
|
+
H = -excess / total_v
|
|
204
|
+
ys.append(-math.log10(H))
|
|
205
|
+
else:
|
|
206
|
+
ys.append(7.0)
|
|
207
|
+
return xs, ys, "Titration Curve: pH vs Volume of Base", "V base (mL)", "pH"
|
|
208
|
+
if name == "energy_levels":
|
|
209
|
+
xs = list(range(1, 6))
|
|
210
|
+
ys = [-2.18e-18 / (n_ * n_) for n_ in xs]
|
|
211
|
+
return xs, ys, "Bohr Energy Levels (Hydrogen)", "n", "E (J)"
|
|
212
|
+
raise ValueError(f"Unknown preset: {name}")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
PRESETS = [
|
|
216
|
+
("zero_order", "Zero Order kinetics: [R] vs t"),
|
|
217
|
+
("first_order", "First Order kinetics: [R] vs t (exponential decay)"),
|
|
218
|
+
("first_order_log", "First Order: log[R] vs t (linear form)"),
|
|
219
|
+
("second_order", "Second Order kinetics: [R] vs t"),
|
|
220
|
+
("arrhenius", "Arrhenius plot: ln k vs 1/T"),
|
|
221
|
+
("boyles", "Boyle's Law: V vs P"),
|
|
222
|
+
("maxwell_boltzmann", "Maxwell-Boltzmann speed distribution"),
|
|
223
|
+
("radial_prob_1s", "1s orbital radial probability density"),
|
|
224
|
+
("titration", "Strong acid / strong base titration curve"),
|
|
225
|
+
("energy_levels", "Bohr hydrogen energy levels"),
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ------------------------------------------------------------ ascii plot --
|
|
230
|
+
def ascii_plot(xs, ys, title="", xlabel="x", ylabel="y", width=64, height=18,
|
|
231
|
+
animate=True, out=sys.stdout, color=None):
|
|
232
|
+
color = color or theme.CYAN
|
|
233
|
+
ymin, ymax = min(ys), max(ys)
|
|
234
|
+
xmin, xmax = min(xs), max(xs)
|
|
235
|
+
if ymax == ymin:
|
|
236
|
+
ymax += 1
|
|
237
|
+
if xmax == xmin:
|
|
238
|
+
xmax += 1
|
|
239
|
+
|
|
240
|
+
grid = [[" "] * width for _ in range(height)]
|
|
241
|
+
|
|
242
|
+
def gx(x): return int((x - xmin) / (xmax - xmin) * (width - 1))
|
|
243
|
+
def gy(y): return int(height - 1 - (y - ymin) / (ymax - ymin) * (height - 1))
|
|
244
|
+
|
|
245
|
+
points = [(gx(x), gy(y)) for x, y in zip(xs, ys)]
|
|
246
|
+
|
|
247
|
+
def draw_upto(idx):
|
|
248
|
+
for i in range(1, idx + 1):
|
|
249
|
+
x0, y0 = points[i - 1]
|
|
250
|
+
x1, y1 = points[i]
|
|
251
|
+
steps = max(abs(x1 - x0), abs(y1 - y0), 1)
|
|
252
|
+
for s in range(steps + 1):
|
|
253
|
+
xx = round(x0 + (x1 - x0) * s / steps)
|
|
254
|
+
yy = round(y0 + (y1 - y0) * s / steps)
|
|
255
|
+
if 0 <= yy < height and 0 <= xx < width:
|
|
256
|
+
grid[yy][xx] = "\u25cf"
|
|
257
|
+
|
|
258
|
+
def render():
|
|
259
|
+
lines = []
|
|
260
|
+
y_at = lambda row: ymax - row * (ymax - ymin) / (height - 1)
|
|
261
|
+
for r, row in enumerate(grid):
|
|
262
|
+
label = f"{y_at(r):>9.3g} |" if r % 3 == 0 else " " * 9 + " |"
|
|
263
|
+
lines.append(theme.faint(label) + theme.fg("".join(row), color))
|
|
264
|
+
axis = " " * 10 + "-" * width
|
|
265
|
+
lines.append(theme.faint(axis))
|
|
266
|
+
lines.append(" " * 10 + theme.dim(f"{xmin:.3g}") +
|
|
267
|
+
" " * (width - len(f"{xmin:.3g}") - len(f"{xmax:.3g}")) +
|
|
268
|
+
theme.dim(f"{xmax:.3g}"))
|
|
269
|
+
return lines
|
|
270
|
+
|
|
271
|
+
if title:
|
|
272
|
+
print(theme.purple(title, bold=True))
|
|
273
|
+
if animate:
|
|
274
|
+
for i in range(1, len(points), max(1, len(points) // 40)):
|
|
275
|
+
draw_upto(i)
|
|
276
|
+
out.write("\033[H" if i > 1 else "")
|
|
277
|
+
for l in render():
|
|
278
|
+
out.write(l + "\n")
|
|
279
|
+
out.flush()
|
|
280
|
+
time.sleep(0.02)
|
|
281
|
+
draw_upto(len(points) - 1)
|
|
282
|
+
for l in render():
|
|
283
|
+
print(l)
|
|
284
|
+
print(theme.dim(f" x: {xlabel} y: {ylabel}"))
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# --------------------------------------------------------- image export --
|
|
288
|
+
def _ensure_dir():
|
|
289
|
+
os.makedirs(EXPORT_DIR, exist_ok=True)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def export_2d(xs, ys, title, xlabel, ylabel, filename=None):
|
|
293
|
+
import matplotlib
|
|
294
|
+
matplotlib.use("Agg")
|
|
295
|
+
import matplotlib.pyplot as plt
|
|
296
|
+
_ensure_dir()
|
|
297
|
+
filename = filename or (title.lower().replace(" ", "_").replace(":", "") + ".png")
|
|
298
|
+
path = os.path.join(EXPORT_DIR, filename)
|
|
299
|
+
fig, ax = plt.subplots(figsize=(7, 4.5), facecolor="#1a1b26")
|
|
300
|
+
ax.set_facecolor("#1a1b26")
|
|
301
|
+
ax.plot(xs, ys, color="#7dcfff", linewidth=2)
|
|
302
|
+
ax.set_title(title, color="#c0caf5")
|
|
303
|
+
ax.set_xlabel(xlabel, color="#a9b1d6")
|
|
304
|
+
ax.set_ylabel(ylabel, color="#a9b1d6")
|
|
305
|
+
ax.tick_params(colors="#a9b1d6")
|
|
306
|
+
for spine in ax.spines.values():
|
|
307
|
+
spine.set_color("#414868")
|
|
308
|
+
ax.grid(True, color="#292e42", linewidth=0.5)
|
|
309
|
+
fig.tight_layout()
|
|
310
|
+
fig.savefig(path, dpi=150)
|
|
311
|
+
plt.close(fig)
|
|
312
|
+
return path
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def export_3d_surface(kind="orbital_3d", filename=None):
|
|
316
|
+
"""Renders a real 3D surface (matplotlib) for a chemistry concept:
|
|
317
|
+
'orbital_3d' -> a 2p-orbital-shaped probability surface,
|
|
318
|
+
'pvt' -> the ideal-gas P-V-T surface."""
|
|
319
|
+
import numpy as np
|
|
320
|
+
import matplotlib
|
|
321
|
+
matplotlib.use("Agg")
|
|
322
|
+
import matplotlib.pyplot as plt
|
|
323
|
+
from mpl_toolkits.mplot3d import Axes3D # noqa: F401
|
|
324
|
+
_ensure_dir()
|
|
325
|
+
|
|
326
|
+
fig = plt.figure(figsize=(7, 6), facecolor="#1a1b26")
|
|
327
|
+
ax = fig.add_subplot(111, projection="3d")
|
|
328
|
+
ax.set_facecolor("#1a1b26")
|
|
329
|
+
|
|
330
|
+
if kind == "pvt":
|
|
331
|
+
n_ = np.linspace(0.5, 3, 40)
|
|
332
|
+
T = np.linspace(200, 500, 40)
|
|
333
|
+
N, T2 = np.meshgrid(n_, T)
|
|
334
|
+
P = N * 0.0821 * T2 / 10.0 # V fixed at 10 L
|
|
335
|
+
surf = ax.plot_surface(N, T2, P, cmap="plasma", edgecolor="none")
|
|
336
|
+
ax.set_xlabel("n (mol)", color="#a9b1d6")
|
|
337
|
+
ax.set_ylabel("T (K)", color="#a9b1d6")
|
|
338
|
+
ax.set_zlabel("P (atm)", color="#a9b1d6")
|
|
339
|
+
ax.set_title("Ideal Gas Law surface: P(n, T) at V=10L", color="#c0caf5")
|
|
340
|
+
filename = filename or "pvt_surface.png"
|
|
341
|
+
else:
|
|
342
|
+
theta = np.linspace(0, np.pi, 60)
|
|
343
|
+
phi = np.linspace(0, 2 * np.pi, 60)
|
|
344
|
+
TH, PH = np.meshgrid(theta, phi)
|
|
345
|
+
r = np.abs(np.cos(TH)) # 2pz angular shape
|
|
346
|
+
X = r * np.sin(TH) * np.cos(PH)
|
|
347
|
+
Y = r * np.sin(TH) * np.sin(PH)
|
|
348
|
+
Z = r * np.cos(TH)
|
|
349
|
+
surf = ax.plot_surface(X, Y, Z, cmap="cool", edgecolor="none")
|
|
350
|
+
ax.set_title("2p Orbital — angular probability surface", color="#c0caf5")
|
|
351
|
+
filename = filename or "orbital_2p_3d.png"
|
|
352
|
+
|
|
353
|
+
ax.tick_params(colors="#a9b1d6")
|
|
354
|
+
path = os.path.join(EXPORT_DIR, filename)
|
|
355
|
+
fig.tight_layout()
|
|
356
|
+
fig.savefig(path, dpi=150)
|
|
357
|
+
plt.close(fig)
|
|
358
|
+
return path
|