pepflow 0.1.3a1__py3-none-any.whl → 0.1.4a1__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.
pepflow/point_test.py CHANGED
@@ -18,349 +18,147 @@
18
18
  # under the License.
19
19
 
20
20
  import time
21
+ from typing import Iterator
21
22
 
22
23
  import numpy as np
24
+ import pytest
23
25
 
24
26
  from pepflow import expression_manager as exm
25
- from pepflow import function as fc
26
27
  from pepflow import pep as pep
27
- from pepflow import point, scalar, utils
28
+ from pepflow import pep_context as pc
29
+ from pepflow import point
28
30
 
29
31
 
30
- def test_point_hash_different():
31
- pep_builder = pep.PEPBuilder()
32
- with pep_builder.make_context("test"):
33
- p1 = point.Point(is_basis=True, eval_expression=None)
34
- p2 = point.Point(is_basis=True, eval_expression=None)
35
- assert p1.uid != p2.uid
32
+ @pytest.fixture
33
+ def pep_context() -> Iterator[pc.PEPContext]:
34
+ """Prepare the pep context and reset the context to None at the end."""
35
+ ctx = pc.PEPContext("test").set_as_current()
36
+ yield ctx
37
+ pc.set_current_context(None)
36
38
 
37
39
 
38
- def test_scalar_hash_different():
39
- pep_builder = pep.PEPBuilder()
40
- with pep_builder.make_context("test"):
41
- s1 = scalar.Scalar(is_basis=True, eval_expression=None)
42
- s2 = scalar.Scalar(is_basis=True, eval_expression=None)
43
- assert s1.uid != s2.uid
40
+ def test_point_add_tag(pep_context: pc.PEPContext) -> None:
41
+ p1 = point.Point(is_basis=True, eval_expression=None, tags=["p1"])
42
+ p2 = point.Point(is_basis=True, eval_expression=None, tags=["p2"])
44
43
 
44
+ p_add = p1 + p2
45
+ assert p_add.tag == "p1+p2"
45
46
 
46
- def test_point_tag():
47
- pep_builder = pep.PEPBuilder()
48
- with pep_builder.make_context("test"):
49
- p1 = point.Point(is_basis=True, eval_expression=None)
50
- p1.add_tag(tag="my_tag")
51
- assert p1.tags == ["my_tag"]
52
- assert p1.tag == "my_tag"
47
+ p_sub = p1 - p2
48
+ assert p_sub.tag == "p1-p2"
53
49
 
50
+ p_sub = p1 - (p2 + p1)
51
+ assert p_sub.tag == "p1-(p2+p1)"
54
52
 
55
- def test_point_repr():
56
- pep_builder = pep.PEPBuilder()
57
- with pep_builder.make_context("test"):
58
- p1 = point.Point(is_basis=True)
59
- print(p1) # it should be fine without tag
60
- p1.add_tag("my_tag")
61
- assert str(p1) == "my_tag"
53
+ p_sub = p1 - (p2 - p1)
54
+ assert p_sub.tag == "p1-(p2-p1)"
62
55
 
63
56
 
64
- def test_scalar_tag():
65
- pep_builder = pep.PEPBuilder()
66
- with pep_builder.make_context("test"):
67
- s1 = scalar.Scalar(is_basis=True, eval_expression=None)
68
- s1.add_tag(tag="my_tag")
69
- assert s1.tags == ["my_tag"]
70
- assert s1.tag == "my_tag"
57
+ def test_point_mul_tag(pep_context: pc.PEPContext) -> None:
58
+ p = point.Point(is_basis=True, eval_expression=None, tags=["p"])
71
59
 
60
+ p_mul = p * 0.1
61
+ assert p_mul.tag == "p*0.1"
72
62
 
73
- def test_scalar_repr():
74
- pep_builder = pep.PEPBuilder()
75
- with pep_builder.make_context("test"):
76
- s1 = scalar.Scalar(is_basis=True)
77
- print(s1) # it should be fine without tag
78
- s1.add_tag("my_tag")
79
- assert str(s1) == "my_tag"
63
+ p_rmul = 0.1 * p
64
+ assert p_rmul.tag == "0.1*p"
80
65
 
66
+ p_pow = p**2
67
+ assert p_pow.tag == "|p|^2"
81
68
 
82
- def test_point_in_a_list():
83
- pep_builder = pep.PEPBuilder()
84
- with pep_builder.make_context("test"):
85
- p1 = point.Point(is_basis=True, eval_expression=None)
86
- p2 = point.Point(is_basis=True, eval_expression=None)
87
- p3 = point.Point(is_basis=True, eval_expression=None)
88
- assert p1 in [p1, p2]
89
- assert p3 not in [p1, p2]
69
+ p_neg = -p
70
+ assert p_neg.tag == "-p"
90
71
 
72
+ p_truediv = p / 0.1
73
+ assert p_truediv.tag == "1/0.1*p"
91
74
 
92
- def test_scalar_in_a_list():
93
- pep_builder = pep.PEPBuilder()
94
- with pep_builder.make_context("test"):
95
- s1 = scalar.Scalar(is_basis=True, eval_expression=None)
96
- s2 = scalar.Scalar(is_basis=True, eval_expression=None)
97
- s3 = scalar.Scalar(is_basis=True, eval_expression=None)
98
- assert s1 in [s1, s2]
99
- assert s3 not in [s1, s2]
100
75
 
76
+ def test_point_add_and_mul_tag(pep_context: pc.PEPContext) -> None:
77
+ p1 = point.Point(is_basis=True, eval_expression=None, tags=["p1"])
78
+ p2 = point.Point(is_basis=True, eval_expression=None, tags=["p2"])
101
79
 
102
- def test_expression_manager_on_basis_point():
103
- pep_builder = pep.PEPBuilder()
104
- with pep_builder.make_context("test") as ctx:
105
- p1 = point.Point(is_basis=True, eval_expression=None, tags=["p1"])
106
- p2 = point.Point(is_basis=True, eval_expression=None, tags=["p2"])
107
- pm = exm.ExpressionManager(ctx)
80
+ p_add_mul = (p1 + p2) * 0.1
81
+ assert p_add_mul.tag == "(p1+p2)*0.1"
108
82
 
109
- np.testing.assert_allclose(pm.eval_point(p1).vector, np.array([1, 0]))
110
- np.testing.assert_allclose(pm.eval_point(p2).vector, np.array([0, 1]))
83
+ p_add_mul = (p1 + p2) * (p1 + p2)
84
+ assert p_add_mul.tag == "(p1+p2)*(p1+p2)"
111
85
 
112
- p3 = point.Point(is_basis=True, eval_expression=None, tags=["p3"]) # noqa: F841
113
- pm = exm.ExpressionManager(ctx)
86
+ p_add_pow = (p1 + p2) ** 2
87
+ assert p_add_pow.tag == "|p1+p2|^2"
114
88
 
115
- np.testing.assert_allclose(pm.eval_point(p1).vector, np.array([1, 0, 0]))
116
- np.testing.assert_allclose(pm.eval_point(p2).vector, np.array([0, 1, 0]))
89
+ p_add_mul = p1 + p2 * 0.1
90
+ assert p_add_mul.tag == "p1+p2*0.1"
117
91
 
92
+ p_neg_add = -(p1 + p2)
93
+ assert p_neg_add.tag == "-(p1+p2)"
118
94
 
119
- def test_expression_manager_on_basis_scalar():
120
- pep_builder = pep.PEPBuilder()
121
- with pep_builder.make_context("test") as ctx:
122
- s1 = scalar.Scalar(is_basis=True, eval_expression=None, tags=["s1"])
123
- s2 = scalar.Scalar(is_basis=True, eval_expression=None, tags=["s2"])
124
- pm = exm.ExpressionManager(ctx)
95
+ p_rmul_add = 0.1 * (p1 + p2)
96
+ assert p_rmul_add.tag == "0.1*(p1+p2)"
125
97
 
126
- np.testing.assert_allclose(pm.eval_scalar(s1).vector, np.array([1, 0]))
127
- np.testing.assert_allclose(pm.eval_scalar(s2).vector, np.array([0, 1]))
128
98
 
129
- s3 = scalar.Scalar(is_basis=True, eval_expression=None, tags=["s3"]) # noqa: F841
130
- pm = exm.ExpressionManager(ctx)
99
+ def test_point_hash_different(pep_context: pc.PEPContext) -> None:
100
+ p1 = point.Point(is_basis=True, eval_expression=None)
101
+ p2 = point.Point(is_basis=True, eval_expression=None)
102
+ assert p1.uid != p2.uid
103
+
104
+
105
+ def test_point_tag(pep_context: pc.PEPContext) -> None:
106
+ p1 = point.Point(is_basis=True, eval_expression=None)
107
+ p1.add_tag(tag="my_tag")
108
+ assert p1.tags == ["my_tag"]
109
+ assert p1.tag == "my_tag"
131
110
 
132
- np.testing.assert_allclose(pm.eval_scalar(s1).vector, np.array([1, 0, 0]))
133
- np.testing.assert_allclose(pm.eval_scalar(s2).vector, np.array([0, 1, 0]))
134
111
 
112
+ def test_point_repr(pep_context: pc.PEPContext) -> None:
113
+ p1 = point.Point(is_basis=True)
114
+ assert str(p1) is not None # it should be fine without tag
115
+ p1.add_tag("my_tag")
116
+ assert str(p1) == "my_tag"
135
117
 
136
- def test_expression_manager_eval_point():
137
- pep_builder = pep.PEPBuilder()
138
- with pep_builder.make_context("test") as ctx:
139
- p1 = point.Point(is_basis=True)
140
- p2 = point.Point(is_basis=True)
141
- p3 = 2 * p1 + p2 / 4
142
- p4 = p3 + p1
143
118
 
144
- pm = exm.ExpressionManager(ctx)
119
+ def test_point_in_a_list(pep_context: pc.PEPContext):
120
+ p1 = point.Point(is_basis=True, eval_expression=None)
121
+ p2 = point.Point(is_basis=True, eval_expression=None)
122
+ p3 = point.Point(is_basis=True, eval_expression=None)
123
+ assert p1 in [p1, p2]
124
+ assert p3 not in [p1, p2]
125
+
126
+
127
+ def test_expression_manager_on_basis_point(pep_context: pc.PEPContext):
128
+ p1 = point.Point(is_basis=True, eval_expression=None, tags=["p1"])
129
+ p2 = point.Point(is_basis=True, eval_expression=None, tags=["p2"])
130
+ pm = exm.ExpressionManager(pep_context)
131
+
132
+ np.testing.assert_allclose(pm.eval_point(p1).vector, np.array([1, 0]))
133
+ np.testing.assert_allclose(pm.eval_point(p2).vector, np.array([0, 1]))
134
+
135
+ p3 = point.Point(is_basis=True, eval_expression=None, tags=["p3"]) # noqa: F841
136
+ pm = exm.ExpressionManager(pep_context)
137
+
138
+ np.testing.assert_allclose(pm.eval_point(p1).vector, np.array([1, 0, 0]))
139
+ np.testing.assert_allclose(pm.eval_point(p2).vector, np.array([0, 1, 0]))
140
+
141
+
142
+ def test_expression_manager_eval_point(pep_context: pc.PEPContext):
143
+ p1 = point.Point(is_basis=True, tags=["p1"])
144
+ p2 = point.Point(is_basis=True, tags=["p2"])
145
+ p3 = 2 * p1 + p2 / 4
146
+ p4 = p3 + p1
147
+
148
+ pm = exm.ExpressionManager(pep_context)
145
149
  np.testing.assert_allclose(pm.eval_point(p3).vector, np.array([2, 0.25]))
146
150
  np.testing.assert_allclose(pm.eval_point(p4).vector, np.array([3, 0.25]))
147
151
 
148
152
 
149
- def test_expression_manager_eval_scalar():
150
- pep_builder = pep.PEPBuilder()
151
- with pep_builder.make_context("test") as ctx:
152
- s1 = scalar.Scalar(is_basis=True)
153
- s2 = scalar.Scalar(is_basis=True)
154
- s3 = 2 * s1 + s2 / 4 + 5
155
- s4 = s3 + s1
156
- s5 = s4 + 5
157
-
158
- p1 = point.Point(is_basis=True)
159
- p2 = point.Point(is_basis=True)
160
- s6 = p1 * p2
161
-
162
- p3 = point.Point(is_basis=True)
163
- p4 = point.Point(is_basis=True)
164
- s7 = 5 * p3 * p4
165
-
166
- s8 = s6 + s7
167
-
168
- pm = exm.ExpressionManager(ctx)
169
-
170
- np.testing.assert_allclose(pm.eval_scalar(s3).vector, np.array([2, 0.25]))
171
- np.testing.assert_allclose(pm.eval_scalar(s3).constant, 5)
172
- np.testing.assert_allclose(pm.eval_scalar(s4).vector, np.array([3, 0.25]))
173
- np.testing.assert_allclose(pm.eval_scalar(s5).vector, np.array([3, 0.25]))
174
- np.testing.assert_allclose(pm.eval_scalar(s5).constant, 10)
175
-
176
- np.testing.assert_allclose(pm.eval_point(p1).vector, np.array([1, 0, 0, 0]))
177
- np.testing.assert_allclose(pm.eval_point(p2).vector, np.array([0, 1, 0, 0]))
178
- np.testing.assert_allclose(pm.eval_point(p3).vector, np.array([0, 0, 1, 0]))
179
- np.testing.assert_allclose(pm.eval_point(p4).vector, np.array([0, 0, 0, 1]))
180
-
181
- np.testing.assert_allclose(
182
- pm.eval_scalar(s6).matrix,
183
- np.array(
184
- [
185
- [0.0, 0.5, 0.0, 0.0],
186
- [0.5, 0.0, 0.0, 0.0],
187
- [0.0, 0.0, 0.0, 0.0],
188
- [0.0, 0.0, 0.0, 0.0],
189
- ]
190
- ),
191
- )
192
- np.testing.assert_allclose(
193
- pm.eval_scalar(s7).matrix,
194
- np.array(
195
- [
196
- [0.0, 0.0, 0.0, 0.0],
197
- [0.0, 0.0, 0.0, 0.0],
198
- [0.0, 0.0, 0.0, 2.5],
199
- [0.0, 0.0, 2.5, 0.0],
200
- ]
201
- ),
202
- )
203
-
204
- np.testing.assert_allclose(
205
- pm.eval_scalar(s8).matrix,
206
- np.array(
207
- [
208
- [0.0, 0.5, 0.0, 0.0],
209
- [0.5, 0.0, 0.0, 0.0],
210
- [0.0, 0.0, 0.0, 2.5],
211
- [0.0, 0.0, 2.5, 0.0],
212
- ]
213
- ),
214
- )
215
-
216
-
217
- def test_constraint():
218
- pep_builder = pep.PEPBuilder()
219
- with pep_builder.make_context("test") as ctx:
220
- s1 = scalar.Scalar(is_basis=True)
221
- s2 = scalar.Scalar(is_basis=True)
222
- s3 = 2 * s1 + s2 / 4 + 5
223
-
224
- c1 = s3.le(5, name="c1")
225
- c2 = s3.lt(5, name="c2")
226
- c3 = s3.ge(5, name="c3")
227
- c4 = s3.gt(5, name="c4")
228
- c5 = s3.eq(5, name="c5")
229
-
230
- pm = exm.ExpressionManager(ctx)
231
-
232
- np.testing.assert_allclose(pm.eval_scalar(c1.scalar).vector, np.array([2, 0.25]))
233
- np.testing.assert_allclose(pm.eval_scalar(c1.scalar).constant, 0)
234
- assert c1.comparator == utils.Comparator.LT
235
-
236
- np.testing.assert_allclose(pm.eval_scalar(c2.scalar).vector, np.array([2, 0.25]))
237
- np.testing.assert_allclose(pm.eval_scalar(c2.scalar).constant, 0)
238
- assert c2.comparator == utils.Comparator.LT
239
-
240
- np.testing.assert_allclose(pm.eval_scalar(c3.scalar).vector, np.array([2, 0.25]))
241
- np.testing.assert_allclose(pm.eval_scalar(c3.scalar).constant, 0)
242
- assert c3.comparator == utils.Comparator.GT
243
-
244
- np.testing.assert_allclose(pm.eval_scalar(c4.scalar).vector, np.array([2, 0.25]))
245
- np.testing.assert_allclose(pm.eval_scalar(c4.scalar).constant, 0)
246
- assert c4.comparator == utils.Comparator.GT
247
-
248
- np.testing.assert_allclose(pm.eval_scalar(c5.scalar).vector, np.array([2, 0.25]))
249
- np.testing.assert_allclose(pm.eval_scalar(c5.scalar).constant, 0)
250
- assert c5.comparator == utils.Comparator.EQ
251
-
252
-
253
- def test_expression_manager_eval_point_large_scale():
254
- pep_builder = pep.PEPBuilder()
255
- with pep_builder.make_context("test") as ctx:
256
- all_basis = [point.Point(is_basis=True) for _ in range(100)]
257
- p = all_basis[0]
258
- for i in range(len(all_basis)):
259
- for j in range(i + 1, len(all_basis)):
260
- p += all_basis[i] * 2 + all_basis[j]
261
- pm = exm.ExpressionManager(ctx)
262
- t = time.time()
263
- for pp in ctx.points:
264
- pm.eval_point(pp)
265
-
266
- assert (time.time() - t) < 0.5
267
-
268
-
269
- def test_function_generate_triplet():
270
- pep_builder = pep.PEPBuilder()
271
- with pep_builder.make_context("test") as ctx:
272
- f = fc.Function(is_basis=True, reuse_gradient=True)
273
- f.add_tag("f")
274
- g = fc.Function(is_basis=True, reuse_gradient=True)
275
- g.add_tag("g")
276
- h = 5 * f + 5 * g
277
- h.add_tag("h")
278
-
279
- f1 = fc.Function(is_basis=True, reuse_gradient=False)
280
- f1.add_tag("f1")
281
- g1 = fc.Function(is_basis=True, reuse_gradient=False)
282
- g1.add_tag("g1")
283
- h1 = 5 * f1 + 5 * g1
284
- h1.add_tag("h1")
285
-
286
- p1 = point.Point(is_basis=True)
287
- p1.add_tag("p1")
288
- p1_triplet = h.generate_triplet(p1)
289
- p1_triplet_1 = h.generate_triplet(p1)
290
-
291
- p1_triplet_2 = h1.generate_triplet(p1)
292
- p1_triplet_3 = h1.generate_triplet(p1)
293
-
294
- pm = exm.ExpressionManager(ctx)
295
-
296
- np.testing.assert_allclose(
297
- pm.eval_point(p1).vector, np.array([1, 0, 0, 0, 0, 0, 0])
298
- )
299
-
300
- np.testing.assert_allclose(
301
- pm.eval_point(p1_triplet.gradient).vector, np.array([0, 5, 5, 0, 0, 0, 0])
302
- )
303
- np.testing.assert_allclose(
304
- pm.eval_scalar(p1_triplet.function_value).vector, np.array([5, 5, 0, 0])
305
- )
306
-
307
- np.testing.assert_allclose(
308
- pm.eval_point(p1_triplet_1.gradient).vector, np.array([0, 5, 5, 0, 0, 0, 0])
309
- )
310
- np.testing.assert_allclose(
311
- pm.eval_scalar(p1_triplet_1.function_value).vector, np.array([5, 5, 0, 0])
312
- )
313
-
314
- np.testing.assert_allclose(
315
- pm.eval_point(p1_triplet_2.gradient).vector, np.array([0, 0, 0, 5, 5, 0, 0])
316
- )
317
- np.testing.assert_allclose(
318
- pm.eval_scalar(p1_triplet_2.function_value).vector, np.array([0, 0, 5, 5])
319
- )
320
-
321
- np.testing.assert_allclose(
322
- pm.eval_point(p1_triplet_3.gradient).vector, np.array([0, 0, 0, 0, 0, 5, 5])
323
- )
324
- np.testing.assert_allclose(
325
- pm.eval_scalar(p1_triplet_3.function_value).vector, np.array([0, 0, 5, 5])
326
- )
327
-
328
-
329
- def test_function_add_stationary_point():
330
- pep_builder = pep.PEPBuilder()
331
- with pep_builder.make_context("test") as ctx:
332
- f = fc.Function(is_basis=True, reuse_gradient=True)
333
- f.add_tag("f")
334
- x_opt = f.add_stationary_point("x_opt")
335
-
336
- pm = exm.ExpressionManager(ctx)
337
-
338
- np.testing.assert_allclose(pm.eval_point(x_opt).vector, np.array([1]))
339
-
340
-
341
- def test_smooth_interpolability_constraints():
342
- pep_builder = pep.PEPBuilder()
343
- with pep_builder.make_context("test") as ctx:
344
- f = fc.SmoothConvexFunction(L=1)
345
- f.add_tag("f")
346
- _ = f.add_stationary_point("x_opt")
347
-
348
- x_0 = point.Point(is_basis=True)
349
- x_0.add_tag("x_0")
350
- _ = f.generate_triplet(x_0)
351
-
352
- all_interpolation_constraints = f.get_interpolation_constraints()
353
-
354
- pm = exm.ExpressionManager(ctx)
355
-
356
- np.testing.assert_allclose(
357
- pm.eval_scalar(all_interpolation_constraints[1].scalar).vector, [1, -1]
358
- )
359
- np.testing.assert_allclose(
360
- pm.eval_scalar(all_interpolation_constraints[1].scalar).matrix,
361
- [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.5]],
362
- )
363
-
364
- np.testing.assert_allclose(
365
- pm.eval_scalar(all_interpolation_constraints[1].scalar).constant, 0
366
- )
153
+ def test_expression_manager_eval_point_large_scale(pep_context):
154
+ all_basis = [point.Point(is_basis=True, tags=[f"p_{i}"]) for i in range(100)]
155
+ p = all_basis[0]
156
+ for i in range(len(all_basis)):
157
+ for j in range(i + 1, len(all_basis)):
158
+ p += all_basis[i] * 2 + all_basis[j]
159
+ pm = exm.ExpressionManager(pep_context)
160
+ t = time.time()
161
+ for pp in pep_context.points:
162
+ pm.eval_point(pp)
163
+
164
+ assert (time.time() - t) < 0.5
pepflow/scalar.py CHANGED
@@ -173,56 +173,94 @@ class Scalar:
173
173
  return self.tag
174
174
  return super().__repr__()
175
175
 
176
+ def _repr_latex_(self):
177
+ s = repr(self)
178
+ s = s.replace("star", r"\star")
179
+ s = s.replace("gradient_", r"\nabla ")
180
+ return rf"$\\displaystyle {s}$"
181
+
176
182
  def __add__(self, other):
177
183
  assert is_numerical_or_scalar(other)
184
+ if utils.is_numerical(other):
185
+ tag_other = f"{other:.4g}"
186
+ else:
187
+ tag_other = other.tag
178
188
  return Scalar(
179
189
  is_basis=False,
180
190
  eval_expression=EvalExpressionScalar(utils.Op.ADD, self, other),
191
+ tags=[f"{self.tag}+{tag_other}"],
181
192
  )
182
193
 
183
194
  def __radd__(self, other):
184
195
  assert is_numerical_or_scalar(other)
196
+ if utils.is_numerical(other):
197
+ tag_other = f"{other:.4g}"
198
+ else:
199
+ tag_other = other.tag
185
200
  return Scalar(
186
201
  is_basis=False,
187
202
  eval_expression=EvalExpressionScalar(utils.Op.ADD, other, self),
203
+ tags=[f"{tag_other}+{self.tag}"],
188
204
  )
189
205
 
190
206
  def __sub__(self, other):
191
207
  assert is_numerical_or_scalar(other)
208
+ if utils.is_numerical(other):
209
+ tag_other = f"{other:.4g}"
210
+ else:
211
+ tag_other = utils.parenthesize_tag(other)
192
212
  return Scalar(
193
213
  is_basis=False,
194
214
  eval_expression=EvalExpressionScalar(utils.Op.SUB, self, other),
215
+ tags=[f"{self.tag}-{tag_other}"],
195
216
  )
196
217
 
197
218
  def __rsub__(self, other):
198
219
  assert is_numerical_or_scalar(other)
220
+ tag_self = utils.parenthesize_tag(self)
221
+ if utils.is_numerical(other):
222
+ tag_other = f"{other:.4g}"
223
+ else:
224
+ tag_other = other.tag
199
225
  return Scalar(
200
226
  is_basis=False,
201
227
  eval_expression=EvalExpressionScalar(utils.Op.SUB, other, self),
228
+ tags=[f"{tag_other}-{tag_self}"],
202
229
  )
203
230
 
204
231
  def __mul__(self, other):
205
232
  assert utils.is_numerical(other)
233
+ tag_self = utils.parenthesize_tag(self)
206
234
  return Scalar(
207
235
  is_basis=False,
208
236
  eval_expression=EvalExpressionScalar(utils.Op.MUL, self, other),
237
+ tags=[f"{tag_self}*{other:.4g}"],
209
238
  )
210
239
 
211
240
  def __rmul__(self, other):
212
241
  assert utils.is_numerical(other)
242
+ tag_self = utils.parenthesize_tag(self)
213
243
  return Scalar(
214
244
  is_basis=False,
215
245
  eval_expression=EvalExpressionScalar(utils.Op.MUL, other, self),
246
+ tags=[f"{other:.4g}*{tag_self}"],
216
247
  )
217
248
 
218
249
  def __neg__(self):
219
- return self.__rmul__(other=-1)
250
+ tag_self = utils.parenthesize_tag(self)
251
+ return Scalar(
252
+ is_basis=False,
253
+ eval_expression=EvalExpressionScalar(utils.Op.MUL, -1, self),
254
+ tags=[f"-{tag_self}"],
255
+ )
220
256
 
221
257
  def __truediv__(self, other):
222
258
  assert utils.is_numerical(other)
259
+ tag_self = utils.parenthesize_tag(self)
223
260
  return Scalar(
224
261
  is_basis=False,
225
262
  eval_expression=EvalExpressionScalar(utils.Op.DIV, self, other),
263
+ tags=[f"1/{other:.4g}*{tag_self}"],
226
264
  )
227
265
 
228
266
  def __hash__(self):