QuLab 2.0.0__tar.gz → 2.0.2__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 (101) hide show
  1. {QuLab-2.0.0 → qulab-2.0.2}/PKG-INFO +4 -1
  2. {QuLab-2.0.0 → qulab-2.0.2}/QuLab.egg-info/PKG-INFO +4 -1
  3. {QuLab-2.0.0 → qulab-2.0.2}/QuLab.egg-info/SOURCES.txt +25 -13
  4. qulab-2.0.2/QuLab.egg-info/entry_points.txt +2 -0
  5. {QuLab-2.0.0 → qulab-2.0.2}/QuLab.egg-info/requires.txt +3 -0
  6. {QuLab-2.0.0 → qulab-2.0.2}/pyproject.toml +7 -1
  7. qulab-2.0.2/qulab/__main__.py +26 -0
  8. qulab-2.0.2/qulab/monitor/__init__.py +1 -0
  9. qulab-2.0.2/qulab/monitor/__main__.py +8 -0
  10. QuLab-2.0.0/qulab/monitor/__init__.py → qulab-2.0.2/qulab/monitor/monitor.py +2 -2
  11. qulab-2.0.2/qulab/scan/__init__.py +3 -0
  12. qulab-2.0.2/qulab/scan/curd.py +144 -0
  13. {QuLab-2.0.0 → qulab-2.0.2}/qulab/scan/expression.py +150 -5
  14. qulab-2.0.2/qulab/scan/models.py +540 -0
  15. qulab-2.0.2/qulab/scan/optimize.py +69 -0
  16. qulab-2.0.2/qulab/scan/query_record.py +361 -0
  17. qulab-2.0.2/qulab/scan/recorder.py +447 -0
  18. qulab-2.0.2/qulab/scan/scan.py +701 -0
  19. qulab-2.0.2/qulab/sys/rpc/zmq_socket.py +209 -0
  20. qulab-2.0.2/qulab/version.py +1 -0
  21. qulab-2.0.2/qulab/visualization/__init__.py +188 -0
  22. qulab-2.0.2/qulab/visualization/__main__.py +71 -0
  23. qulab-2.0.2/qulab/visualization/_autoplot.py +463 -0
  24. qulab-2.0.2/qulab/visualization/plot_layout.py +408 -0
  25. qulab-2.0.2/qulab/visualization/plot_seq.py +90 -0
  26. qulab-2.0.2/qulab/visualization/qdat.py +152 -0
  27. qulab-2.0.2/qulab/visualization/widgets.py +86 -0
  28. {QuLab-2.0.0 → qulab-2.0.2}/setup.py +1 -1
  29. qulab-2.0.2/tests/test_scan.py +2 -0
  30. QuLab-2.0.0/qulab/monitor/multiploter/__init__.py +0 -1
  31. QuLab-2.0.0/qulab/scan/__init__.py +0 -4
  32. QuLab-2.0.0/qulab/scan/base.py +0 -544
  33. QuLab-2.0.0/qulab/scan/dataset.py +0 -0
  34. QuLab-2.0.0/qulab/scan/optimize.py +0 -0
  35. QuLab-2.0.0/qulab/scan/scanner.py +0 -244
  36. QuLab-2.0.0/qulab/scan/transforms.py +0 -16
  37. QuLab-2.0.0/qulab/version.py +0 -1
  38. QuLab-2.0.0/tests/test_scan_iter.py +0 -375
  39. {QuLab-2.0.0 → qulab-2.0.2}/LICENSE +0 -0
  40. {QuLab-2.0.0 → qulab-2.0.2}/MANIFEST.in +0 -0
  41. {QuLab-2.0.0 → qulab-2.0.2}/QuLab.egg-info/dependency_links.txt +0 -0
  42. {QuLab-2.0.0 → qulab-2.0.2}/QuLab.egg-info/top_level.txt +0 -0
  43. {QuLab-2.0.0 → qulab-2.0.2}/README.md +0 -0
  44. {QuLab-2.0.0 → qulab-2.0.2}/qulab/__init__.py +0 -0
  45. {QuLab-2.0.0/qulab/monitor/multiploter → qulab-2.0.2/qulab/monitor}/config.py +0 -0
  46. {QuLab-2.0.0/qulab/monitor/multiploter → qulab-2.0.2/qulab/monitor}/dataset.py +0 -0
  47. {QuLab-2.0.0/qulab/monitor/multiploter → qulab-2.0.2/qulab/monitor}/event_queue.py +0 -0
  48. /QuLab-2.0.0/qulab/monitor/multiploter/main.py → /qulab-2.0.2/qulab/monitor/mainwindow.py +0 -0
  49. {QuLab-2.0.0/qulab/monitor/multiploter → qulab-2.0.2/qulab/monitor}/ploter.py +0 -0
  50. {QuLab-2.0.0/qulab/monitor/multiploter → qulab-2.0.2/qulab/monitor}/qt_compat.py +0 -0
  51. {QuLab-2.0.0/qulab/monitor/multiploter → qulab-2.0.2/qulab/monitor}/toolbar.py +0 -0
  52. {QuLab-2.0.0 → qulab-2.0.2}/qulab/scan/utils.py +0 -0
  53. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/__init__.py +0 -0
  54. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/__main__.py +0 -0
  55. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/backend/__init__.py +0 -0
  56. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/backend/redis.py +0 -0
  57. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/base_dataset.py +0 -0
  58. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/chunk.py +0 -0
  59. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/dataset.py +0 -0
  60. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/file.py +0 -0
  61. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/__init__.py +0 -0
  62. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/base.py +0 -0
  63. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/config.py +0 -0
  64. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/file.py +0 -0
  65. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/ipy.py +0 -0
  66. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/models.py +0 -0
  67. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/record.py +0 -0
  68. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/report.py +0 -0
  69. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/models/tag.py +0 -0
  70. {QuLab-2.0.0 → qulab-2.0.2}/qulab/storage/storage.py +0 -0
  71. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/__init__.py +0 -0
  72. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/chat.py +0 -0
  73. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/device/__init__.py +0 -0
  74. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/device/basedevice.py +0 -0
  75. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/device/loader.py +0 -0
  76. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/device/utils.py +0 -0
  77. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/drivers/FakeInstrument.py +0 -0
  78. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/drivers/__init__.py +0 -0
  79. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/ipy_events.py +0 -0
  80. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/net/__init__.py +0 -0
  81. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/net/bencoder.py +0 -0
  82. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/net/cli.py +0 -0
  83. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/net/dhcp.py +0 -0
  84. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/net/dhcpd.py +0 -0
  85. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/net/kad.py +0 -0
  86. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/net/kcp.py +0 -0
  87. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/net/nginx.py +0 -0
  88. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/progress.py +0 -0
  89. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/__init__.py +0 -0
  90. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/client.py +0 -0
  91. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/exceptions.py +0 -0
  92. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/msgpack.py +0 -0
  93. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/msgpack.pyi +0 -0
  94. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/rpc.py +0 -0
  95. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/serialize.py +0 -0
  96. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/server.py +0 -0
  97. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/socket.py +0 -0
  98. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/utils.py +0 -0
  99. {QuLab-2.0.0 → qulab-2.0.2}/qulab/sys/rpc/worker.py +0 -0
  100. {QuLab-2.0.0 → qulab-2.0.2}/setup.cfg +0 -0
  101. {QuLab-2.0.0 → qulab-2.0.2}/src/qulab.h +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -30,10 +30,13 @@ Requires-Dist: dill>=0.3.6
30
30
  Requires-Dist: GitPython>=3.1.14
31
31
  Requires-Dist: ipython>=7.4.0
32
32
  Requires-Dist: ipywidgets>=7.4.2
33
+ Requires-Dist: loguru>=0.7.2
33
34
  Requires-Dist: matplotlib>=3.7.2
34
35
  Requires-Dist: numpy>=1.13.3
35
36
  Requires-Dist: ply>=3.11
37
+ Requires-Dist: pyzmq>=25.1.0
36
38
  Requires-Dist: scipy>=1.0.0
39
+ Requires-Dist: watchdog>=4.0.0
37
40
 
38
41
  # QuLab
39
42
  [![View build status](https://travis-ci.org/feihoo87/QuLab.svg?branch=master)](https://travis-ci.org/feihoo87/QuLab)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -30,10 +30,13 @@ Requires-Dist: dill>=0.3.6
30
30
  Requires-Dist: GitPython>=3.1.14
31
31
  Requires-Dist: ipython>=7.4.0
32
32
  Requires-Dist: ipywidgets>=7.4.2
33
+ Requires-Dist: loguru>=0.7.2
33
34
  Requires-Dist: matplotlib>=3.7.2
34
35
  Requires-Dist: numpy>=1.13.3
35
36
  Requires-Dist: ply>=3.11
37
+ Requires-Dist: pyzmq>=25.1.0
36
38
  Requires-Dist: scipy>=1.0.0
39
+ Requires-Dist: watchdog>=4.0.0
37
40
 
38
41
  # QuLab
39
42
  [![View build status](https://travis-ci.org/feihoo87/QuLab.svg?branch=master)](https://travis-ci.org/feihoo87/QuLab)
@@ -6,26 +6,30 @@ setup.py
6
6
  QuLab.egg-info/PKG-INFO
7
7
  QuLab.egg-info/SOURCES.txt
8
8
  QuLab.egg-info/dependency_links.txt
9
+ QuLab.egg-info/entry_points.txt
9
10
  QuLab.egg-info/requires.txt
10
11
  QuLab.egg-info/top_level.txt
11
12
  qulab/__init__.py
13
+ qulab/__main__.py
12
14
  qulab/version.py
13
15
  qulab/monitor/__init__.py
14
- qulab/monitor/multiploter/__init__.py
15
- qulab/monitor/multiploter/config.py
16
- qulab/monitor/multiploter/dataset.py
17
- qulab/monitor/multiploter/event_queue.py
18
- qulab/monitor/multiploter/main.py
19
- qulab/monitor/multiploter/ploter.py
20
- qulab/monitor/multiploter/qt_compat.py
21
- qulab/monitor/multiploter/toolbar.py
16
+ qulab/monitor/__main__.py
17
+ qulab/monitor/config.py
18
+ qulab/monitor/dataset.py
19
+ qulab/monitor/event_queue.py
20
+ qulab/monitor/mainwindow.py
21
+ qulab/monitor/monitor.py
22
+ qulab/monitor/ploter.py
23
+ qulab/monitor/qt_compat.py
24
+ qulab/monitor/toolbar.py
22
25
  qulab/scan/__init__.py
23
- qulab/scan/base.py
24
- qulab/scan/dataset.py
26
+ qulab/scan/curd.py
25
27
  qulab/scan/expression.py
28
+ qulab/scan/models.py
26
29
  qulab/scan/optimize.py
27
- qulab/scan/scanner.py
28
- qulab/scan/transforms.py
30
+ qulab/scan/query_record.py
31
+ qulab/scan/recorder.py
32
+ qulab/scan/scan.py
29
33
  qulab/scan/utils.py
30
34
  qulab/storage/__init__.py
31
35
  qulab/storage/__main__.py
@@ -74,5 +78,13 @@ qulab/sys/rpc/server.py
74
78
  qulab/sys/rpc/socket.py
75
79
  qulab/sys/rpc/utils.py
76
80
  qulab/sys/rpc/worker.py
81
+ qulab/sys/rpc/zmq_socket.py
82
+ qulab/visualization/__init__.py
83
+ qulab/visualization/__main__.py
84
+ qulab/visualization/_autoplot.py
85
+ qulab/visualization/plot_layout.py
86
+ qulab/visualization/plot_seq.py
87
+ qulab/visualization/qdat.py
88
+ qulab/visualization/widgets.py
77
89
  src/qulab.h
78
- tests/test_scan_iter.py
90
+ tests/test_scan.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ qulab = qulab.__main__:main
@@ -4,7 +4,10 @@ dill>=0.3.6
4
4
  GitPython>=3.1.14
5
5
  ipython>=7.4.0
6
6
  ipywidgets>=7.4.2
7
+ loguru>=0.7.2
7
8
  matplotlib>=3.7.2
8
9
  numpy>=1.13.3
9
10
  ply>=3.11
11
+ pyzmq>=25.1.0
10
12
  scipy>=1.0.0
13
+ watchdog>=4.0.0
@@ -34,13 +34,19 @@ dependencies = [
34
34
  "GitPython>=3.1.14",
35
35
  "ipython>=7.4.0",
36
36
  "ipywidgets>=7.4.2",
37
+ "loguru>=0.7.2",
37
38
  "matplotlib>=3.7.2",
38
39
  "numpy>=1.13.3",
39
40
  "ply>=3.11",
40
- "scipy>=1.0.0"
41
+ "pyzmq>=25.1.0",
42
+ "scipy>=1.0.0",
43
+ "watchdog>=4.0.0"
41
44
  ]
42
45
  dynamic = ["version"]
43
46
 
47
+ [project.scripts]
48
+ "qulab" = "qulab.__main__:main"
49
+
44
50
  [project.urls]
45
51
  Homepage = "https://github.com/feihoo87/QuLab"
46
52
  "Bug Reports" = "https://github.com/feihoo87/QuLab/issues"
@@ -0,0 +1,26 @@
1
+ import click
2
+
3
+ from .monitor.__main__ import main as monitor
4
+ from .scan.recorder import record
5
+ from .sys.net.cli import dht
6
+ from .visualization.__main__ import plot
7
+
8
+
9
+ @click.group()
10
+ def main():
11
+ pass
12
+
13
+
14
+ @main.command()
15
+ def hello():
16
+ """Print hello world."""
17
+ click.echo('hello, world')
18
+
19
+
20
+ main.add_command(monitor)
21
+ main.add_command(plot)
22
+ main.add_command(dht)
23
+ main.add_command(record)
24
+
25
+ if __name__ == '__main__':
26
+ main()
@@ -0,0 +1 @@
1
+ from .monitor import Monitor, get_monitor
@@ -0,0 +1,8 @@
1
+ import click
2
+
3
+ from .monitor import Monitor
4
+
5
+
6
+ @click.command(name='monitor')
7
+ def main():
8
+ pass
@@ -11,8 +11,8 @@ def main(queue: mp.Queue,
11
11
  ncols: int = 4,
12
12
  minimum_height: int = 400,
13
13
  colors: list[tuple[int, int, int]] = []):
14
- from .multiploter import MainWindow
15
- from .multiploter.qt_compat import QtWidgets
14
+ from .mainwindow import MainWindow
15
+ from .qt_compat import QtWidgets
16
16
 
17
17
  app = QtWidgets.QApplication(sys.argv)
18
18
  main = MainWindow(queue, ncols, minimum_height, colors)
@@ -0,0 +1,3 @@
1
+ from .expression import Expression, Symbol
2
+ from .query_record import get_record, lookup, lookup_list
3
+ from .scan import Scan
@@ -0,0 +1,144 @@
1
+ from datetime import date, datetime, timezone
2
+ from typing import Sequence, Type, Union
3
+
4
+ from sqlalchemy.orm import Query, Session, aliased
5
+ from sqlalchemy.orm.exc import NoResultFound
6
+ from sqlalchemy.orm.session import Session
7
+ from waveforms.dicttree import foldDict
8
+
9
+ from .models import Comment, Record, Report, Sample, Tag
10
+
11
+
12
+ def tag(session: Session, tag_text: str) -> Tag:
13
+ """Get a tag from the database or create a new if not exists."""
14
+ try:
15
+ return session.query(Tag).filter(Tag.text == tag_text).one()
16
+ except NoResultFound:
17
+ tag = Tag(text=tag_text)
18
+ return tag
19
+
20
+
21
+ def tag_it(session: Session, tag_text: str, obj: Union[Sample, Record,
22
+ Report]) -> Tag:
23
+ """Tag an object."""
24
+ if obj.id is None:
25
+ session.add(obj)
26
+ obj.tags.append(tag(session, tag_text))
27
+ else:
28
+ session.query(type(obj)).filter(
29
+ type(obj).id == obj.id).one().tags.append(tag(session, tag_text))
30
+ session.commit()
31
+
32
+
33
+ def get_object_with_tags(session: Session,
34
+ cls: Union[Type[Comment], Type[Sample], Type[Record],
35
+ Type[Report]], *tags: str) -> Query:
36
+ """
37
+ Query objects with the given tags.
38
+
39
+ Parameters
40
+ ----------
41
+ session : :class:`sqlalchemy.orm.Session`
42
+ The database session.
43
+ cls : :class:`sqlalchemy.orm.Mapper`
44
+ The object class.
45
+ tags : str
46
+ The tags.
47
+
48
+ Returns
49
+ -------
50
+ :class:`sqlalchemy.orm.Query`
51
+ The query.
52
+ """
53
+ if isinstance(session, Query):
54
+ q = session
55
+ else:
56
+ q = session.query(cls)
57
+ if not hasattr(cls, 'tags'):
58
+ return []
59
+
60
+ aliase = {tag: aliased(Tag) for tag in tags}
61
+
62
+ for tag, a in aliase.items():
63
+ q = q.join(a, cls.tags)
64
+ if '*' in tag:
65
+ q = q.filter(a.text.like(tag.replace('*', '%')))
66
+ else:
67
+ q = q.filter(a.text == tag)
68
+ return q
69
+
70
+
71
+ def query_record(session: Session,
72
+ offset: int = 0,
73
+ limit: int = 10,
74
+ app: str | None = None,
75
+ tags: Sequence[str] = (),
76
+ before: datetime | date | None = None,
77
+ after: datetime | date | None = None):
78
+ tz_offset = datetime.now(timezone.utc).astimezone().utcoffset()
79
+ table = {'header': ['ID', 'App', 'tags', 'created time'], 'body': []}
80
+ apps = sorted(
81
+ set([
82
+ n for n, *_ in get_object_with_tags(session.query(Record.app),
83
+ Record, *tags).all()
84
+ ]))
85
+ apps = foldDict(dict([(app, None) for app in apps]))
86
+
87
+ query = get_object_with_tags(session, Record, *tags)
88
+
89
+ if app is not None:
90
+ if app.endswith('*'):
91
+ query = query.filter(Record.app.like(app[:-1] + '%'))
92
+ else:
93
+ query = query.filter(Record.app == app)
94
+ if before is not None:
95
+ if isinstance(before, date):
96
+ before = datetime(before.year, before.month, before.day)
97
+ query = query.filter(Record.ctime <= before - tz_offset)
98
+ if after is not None:
99
+ if isinstance(after, date):
100
+ after = datetime(after.year, after.month, after.day)
101
+ query = query.filter(Record.ctime >= after - tz_offset)
102
+ total = query.count()
103
+ for r in query.order_by(Record.ctime.desc()).limit(limit).offset(offset):
104
+ tags = sorted([t.text for t in r.tags])
105
+ ctime = r.ctime + tz_offset
106
+ row = [r.id, r.app, tags, ctime]
107
+ table['body'].append(row)
108
+
109
+ return total, apps, table
110
+
111
+
112
+ def update_tags(session: Session,
113
+ record_id: int,
114
+ tags: Sequence[str],
115
+ append: bool = False):
116
+ record = session.get(Record, record_id)
117
+ if record is None:
118
+ return False
119
+ if append:
120
+ old = [t.text for t in record.tags]
121
+ for t in old:
122
+ if t not in tags:
123
+ tags.append(t)
124
+ record.tags = [tag(session, t) for t in tags]
125
+ try:
126
+ session.commit()
127
+ except Exception:
128
+ session.rollback()
129
+ return False
130
+ return True
131
+
132
+
133
+ def remove_tags(session: Session, record_id: int, tags: Sequence[str]):
134
+ record = session.get(Record, record_id)
135
+ if record is None:
136
+ return False
137
+ old = [t.text for t in record.tags]
138
+ record.tags = [tag(session, t) for t in old if t not in tags]
139
+ try:
140
+ session.commit()
141
+ except Exception:
142
+ session.rollback()
143
+ return False
144
+ return True
@@ -8,6 +8,7 @@ from pyparsing import (CaselessKeyword, Combine, Forward, Group, Keyword,
8
8
  alphanums, alphas, delimitedList, nums, oneOf, opAssoc,
9
9
  pyparsing_common, restOfLine, srange, stringEnd,
10
10
  stringStart)
11
+ from scipy import special
11
12
 
12
13
  LPAREN, RPAREN, LBRACK, RBRACK, LBRACE, RBRACE, DOT, TILDE, BANG, PLUS, MINUS = map(
13
14
  Suppress, "()[]{}.~!+-")
@@ -60,15 +61,45 @@ class Env():
60
61
  self.consts = {}
61
62
  self.variables = {}
62
63
  self.refs = {}
64
+ self.functions = {
65
+ 'sin': np.sin,
66
+ 'cos': np.cos,
67
+ 'tan': np.tan,
68
+ 'pi': np.pi,
69
+ 'e': np.e,
70
+ 'log': np.log,
71
+ 'log2': np.log2,
72
+ 'log10': np.log10,
73
+ 'exp': np.exp,
74
+ 'sqrt': np.sqrt,
75
+ 'abs': np.abs,
76
+ 'sinh': np.sinh,
77
+ 'cosh': np.cosh,
78
+ 'tanh': np.tanh,
79
+ 'arcsin': np.arcsin,
80
+ 'arccos': np.arccos,
81
+ 'arctan': np.arctan,
82
+ 'arctan2': np.arctan2,
83
+ 'arcsinh': np.arcsinh,
84
+ 'arccosh': np.arccosh,
85
+ 'arctanh': np.arctanh,
86
+ 'sinc': np.sinc,
87
+ 'sign': np.sign,
88
+ 'heaviside': np.heaviside,
89
+ 'erf': special.erf,
90
+ 'erfc': special.erfc,
91
+ }
63
92
 
64
93
  def __contains__(self, key):
65
- return key in self.consts or key in self.variables or key in self.refs
94
+ return key in self.consts or key in self.variables or key in self.functions or key in self.refs
66
95
 
67
96
  def __getitem__(self, key):
68
97
  if key in self.consts:
69
98
  return self.consts[key]
70
99
  if key in self.variables:
71
100
  return self.variables[key]
101
+ if key in self.functions:
102
+ return self.functions[key]
72
103
  if key in self.refs:
73
104
  return self[self.refs[key]]
74
105
  raise KeyError(f"Key {key} not found")
@@ -110,6 +141,9 @@ class Env():
110
141
  return key in self.consts
111
142
 
112
143
 
144
+ _default_env = Env()
145
+
146
+
113
147
  class Expression():
114
148
 
115
149
  def __init__(self):
@@ -127,33 +161,85 @@ class Expression():
127
161
  raise NotImplementedError
128
162
 
129
163
  def __add__(self, other):
164
+ if isinstance(other, Expression):
165
+ other = other.eval(_default_env)
166
+ if isinstance(other, ConstType) and other == 0:
167
+ return self
130
168
  return BinaryExpression(self, other, operator.add)
131
169
 
132
170
  def __radd__(self, other):
171
+ if isinstance(other, Expression):
172
+ other = other.eval(_default_env)
173
+ if isinstance(other, ConstType) and other == 0:
174
+ return self
133
175
  return BinaryExpression(other, self, operator.add)
134
176
 
135
177
  def __sub__(self, other):
178
+ if isinstance(other, Expression):
179
+ other = other.eval(_default_env)
180
+ if isinstance(other, ConstType) and other == 0:
181
+ return self
136
182
  return BinaryExpression(self, other, operator.sub)
137
183
 
138
184
  def __rsub__(self, other):
185
+ if isinstance(other, Expression):
186
+ other = other.eval(_default_env)
187
+ if isinstance(other, ConstType) and other == 0:
188
+ return -self
139
189
  return BinaryExpression(other, self, operator.sub)
140
190
 
141
191
  def __mul__(self, other):
192
+ if isinstance(other, Expression):
193
+ other = other.eval(_default_env)
194
+ if isinstance(other, ConstType) and other == 0:
195
+ return 0
196
+ if isinstance(other, ConstType) and other == 1:
197
+ return self
198
+ if isinstance(other, ConstType) and other == -1:
199
+ return -self
142
200
  return BinaryExpression(self, other, operator.mul)
143
201
 
144
202
  def __rmul__(self, other):
203
+ if isinstance(other, Expression):
204
+ other = other.eval(_default_env)
205
+ if isinstance(other, ConstType) and other == 0:
206
+ return 0
207
+ if isinstance(other, ConstType) and other == 1:
208
+ return self
209
+ if isinstance(other, ConstType) and other == -1:
210
+ return -self
145
211
  return BinaryExpression(other, self, operator.mul)
146
212
 
147
213
  def __truediv__(self, other):
214
+ if isinstance(other, Expression):
215
+ other = other.eval(_default_env)
216
+ if isinstance(other, ConstType) and other == 1:
217
+ return self
218
+ if isinstance(other, ConstType) and other == -1:
219
+ return -self
148
220
  return BinaryExpression(self, other, operator.truediv)
149
221
 
150
222
  def __rtruediv__(self, other):
223
+ if isinstance(other, Expression):
224
+ other = other.eval(_default_env)
225
+ if isinstance(other, ConstType) and other == 0:
226
+ return 0
151
227
  return BinaryExpression(other, self, operator.truediv)
152
228
 
153
229
  def __pow__(self, other):
230
+ if isinstance(other, Expression):
231
+ other = other.eval(_default_env)
232
+ if isinstance(other, ConstType) and other == 0:
233
+ return 1
234
+ if isinstance(other, ConstType) and other == 1:
235
+ return self
154
236
  return BinaryExpression(self, other, operator.pow)
155
237
 
156
238
  def __rpow__(self, other):
239
+ if isinstance(other, Expression):
240
+ other = other.eval(_default_env)
241
+ if isinstance(other, ConstType) and other == 0:
242
+ return 0
157
243
  return BinaryExpression(other, self, operator.pow)
158
244
 
159
245
  def __neg__(self):
@@ -163,32 +249,52 @@ class Expression():
163
249
  return UnaryExpression(self, operator.pos)
164
250
 
165
251
  def __eq__(self, other):
252
+ if isinstance(other, Expression):
253
+ other = other.eval(_default_env)
166
254
  return BinaryExpression(self, other, operator.eq)
167
255
 
168
256
  def __ne__(self, other):
257
+ if isinstance(other, Expression):
258
+ other = other.eval(_default_env)
169
259
  return BinaryExpression(self, other, operator.ne)
170
260
 
171
261
  def __lt__(self, other):
262
+ if isinstance(other, Expression):
263
+ other = other.eval(_default_env)
172
264
  return BinaryExpression(self, other, operator.lt)
173
265
 
174
266
  def __le__(self, other):
267
+ if isinstance(other, Expression):
268
+ other = other.eval(_default_env)
175
269
  return BinaryExpression(self, other, operator.le)
176
270
 
177
271
  def __gt__(self, other):
272
+ if isinstance(other, Expression):
273
+ other = other.eval(_default_env)
178
274
  return BinaryExpression(self, other, operator.gt)
179
275
 
180
276
  def __ge__(self, other):
277
+ if isinstance(other, Expression):
278
+ other = other.eval(_default_env)
181
279
  return BinaryExpression(self, other, operator.ge)
182
280
 
183
281
  def __getitem__(self, other):
282
+ if isinstance(other, Expression):
283
+ other = other.eval(_default_env)
184
284
  return ObjectMethod(self, '__getitem__', other)
185
285
 
186
286
  def __getattr__(self, other):
287
+ if isinstance(other, Expression):
288
+ other = other.eval(_default_env)
187
289
  return ObjectMethod(self, '__getattr__', other)
188
290
 
189
291
  def __call__(self, *args):
292
+ args = [
293
+ o.eval(_default_env) if isinstance(o, Expression) else o
294
+ for o in args
295
+ ]
190
296
  return ObjectMethod(self, '__call__', *args)
191
-
297
+
192
298
  def __round__(self, n=None):
193
299
  return self
194
300
 
@@ -217,6 +323,14 @@ class UnaryExpression(Expression):
217
323
  self.a = a
218
324
  self.op = op
219
325
 
326
+ def __getstate__(self) -> dict:
327
+ return {'a': self.a, 'op': self.op}
328
+
329
+ def __setstate__(self, state: dict):
330
+ self.a = state['a']
331
+ self.op = state['op']
332
+ self.cache = _empty
333
+
220
334
  def symbols(self) -> list[str]:
221
335
  if isinstance(self.a, Expression):
222
336
  return self.a.symbols()
@@ -243,7 +357,7 @@ class UnaryExpression(Expression):
243
357
  return self.op(self.a.d(x))
244
358
  else:
245
359
  return 0
246
-
360
+
247
361
  def __repr__(self) -> str:
248
362
  return f"{self.op.__name__}({self.a!r})"
249
363
 
@@ -256,6 +370,15 @@ class BinaryExpression(Expression):
256
370
  self.b = b
257
371
  self.op = op
258
372
 
373
+ def __getstate__(self) -> dict:
374
+ return {'a': self.a, 'b': self.b, 'op': self.op}
375
+
376
+ def __setstate__(self, state: dict):
377
+ self.a = state['a']
378
+ self.b = state['b']
379
+ self.op = state['op']
380
+ self.cache = _empty
381
+
259
382
  def symbols(self) -> list[str]:
260
383
  symbs = set()
261
384
  if isinstance(self.a, Expression):
@@ -300,7 +423,7 @@ class BinaryExpression(Expression):
300
423
  return 0
301
424
  else:
302
425
  return 0
303
-
426
+
304
427
  def __repr__(self) -> str:
305
428
  return f"({self.a!r} {self.op.__name__} {self.b!r})"
306
429
 
@@ -313,6 +436,15 @@ class ObjectMethod(Expression):
313
436
  self.method = method
314
437
  self.args = args
315
438
 
439
+ def __getstate__(self) -> dict:
440
+ return {'obj': self.obj, 'method': self.method, 'args': self.args}
441
+
442
+ def __setstate__(self, state: dict):
443
+ self.obj = state['obj']
444
+ self.method = state['method']
445
+ self.args = state['args']
446
+ self.cache = _empty
447
+
316
448
  def symbols(self) -> list[str]:
317
449
  symbs = set()
318
450
  if isinstance(self.obj, Expression):
@@ -334,6 +466,12 @@ class ObjectMethod(Expression):
334
466
  else:
335
467
  return getattr(obj, self.method)(*args)
336
468
 
469
+ def __repr__(self):
470
+ if self.method == '__call__':
471
+ return f"{self.obj!r}({', '.join(map(repr, self.args))})"
472
+ else:
473
+ return f"{self.obj!r}.{self.method}({', '.join(map(repr, self.args))})"
474
+
337
475
 
338
476
  class Symbol(Expression):
339
477
 
@@ -341,6 +479,13 @@ class Symbol(Expression):
341
479
  super().__init__()
342
480
  self.name = name
343
481
 
482
+ def __getstate__(self) -> dict:
483
+ return {'name': self.name}
484
+
485
+ def __setstate__(self, state: dict):
486
+ self.name = state['name']
487
+ self.cache = _empty
488
+
344
489
  def symbols(self) -> list[str]:
345
490
  return [self.name]
346
491
 
@@ -355,6 +500,6 @@ class Symbol(Expression):
355
500
  return 1
356
501
  else:
357
502
  return 0
358
-
503
+
359
504
  def __repr__(self) -> str:
360
505
  return self.name