demathpy 0.1.1__tar.gz → 0.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: demathpy
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: PDE/ODE math backend
5
5
  Author: Misekai
6
6
  Author-email: Misekai <mcore-us@misekai.net>
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "demathpy"
4
- version = "0.1.1"
4
+ version = "0.1.2"
5
5
  authors = [
6
6
  { name="Misekai", email="mcore-us@misekai.net" },
7
7
  ]
@@ -20,8 +20,6 @@ def _preprocess_expr(expr: str) -> str:
20
20
  expr = (expr or "").strip()
21
21
  expr = re.sub(r"\(\s*approx\s*\)", "", expr, flags=re.IGNORECASE)
22
22
  expr = re.sub(r"\bapprox\b", "", expr, flags=re.IGNORECASE)
23
- if "=" in expr:
24
- expr = expr.split("=")[-1]
25
23
  expr = normalize_symbols(expr)
26
24
  return expr.strip()
27
25
 
@@ -185,6 +183,52 @@ class ODE:
185
183
  def pos(u): return np.maximum(u, 0.0)
186
184
  def sign(u): return np.sign(u)
187
185
  def step_fn(u): return np.heaviside(u, 1.0)
186
+ def heaviside_fn(u, h0=1.0): return np.heaviside(u, h0)
187
+ def clamp(u, lower, upper): return np.clip(u, lower, upper)
188
+
189
+ def elementwise_min(*values):
190
+ if not values:
191
+ raise ValueError("min requires at least one value")
192
+ out = values[0]
193
+ for value in values[1:]:
194
+ out = np.minimum(out, value)
195
+ return out
196
+
197
+ def elementwise_max(*values):
198
+ if not values:
199
+ raise ValueError("max requires at least one value")
200
+ out = values[0]
201
+ for value in values[1:]:
202
+ out = np.maximum(out, value)
203
+ return out
204
+
205
+ def piecewise(*cases):
206
+ if not cases:
207
+ raise ValueError("Piecewise requires at least one case")
208
+
209
+ # Also support numpy-style piecewise(cond, if_true, if_false).
210
+ if len(cases) == 3 and not isinstance(cases[0], (tuple, list)):
211
+ cond, if_true, if_false = cases
212
+ return np.where(cond, if_true, if_false)
213
+
214
+ condlist = []
215
+ choicelist = []
216
+ default = 0.0
217
+
218
+ for case in cases:
219
+ if not isinstance(case, (tuple, list)) or len(case) != 2:
220
+ raise ValueError("Piecewise cases must be (expr, condition) pairs")
221
+ expr, cond = case
222
+ if isinstance(cond, (bool, np.bool_)):
223
+ if bool(cond):
224
+ default = expr
225
+ continue
226
+ condlist.append(cond)
227
+ choicelist.append(expr)
228
+
229
+ if not condlist:
230
+ return np.asarray(default)
231
+ return np.select(condlist, choicelist, default=default)
188
232
 
189
233
  env = {
190
234
  "np": np,
@@ -194,7 +238,13 @@ class ODE:
194
238
  "log": np.log, "log10": np.log10, "log2": np.log2,
195
239
  "exp": np.exp, "sqrt": np.sqrt, "abs": np.abs,
196
240
  "pi": np.pi, "inf": np.inf,
197
- "pos": pos, "sign": sign, "step": step_fn
241
+ "pos": pos, "sign": sign, "step": step_fn,
242
+ "heaviside": heaviside_fn, "Heaviside": heaviside_fn,
243
+ "clamp": clamp, "clip": np.clip,
244
+ "where": np.where,
245
+ "min": elementwise_min, "max": elementwise_max,
246
+ "minimum": np.minimum, "maximum": np.maximum,
247
+ "piecewise": piecewise, "Piecewise": piecewise,
198
248
  }
199
249
 
200
250
  env.update(self.external_variables)
@@ -20,8 +20,6 @@ def _preprocess_expr(expr: str) -> str:
20
20
  expr = (expr or "").strip()
21
21
  expr = re.sub(r"\(\s*approx\s*\)", "", expr, flags=re.IGNORECASE)
22
22
  expr = re.sub(r"\bapprox\b", "", expr, flags=re.IGNORECASE)
23
- if "=" in expr:
24
- expr = expr.split("=")[-1]
25
23
  expr = normalize_symbols(expr)
26
24
  return expr.strip()
27
25
 
@@ -651,6 +649,53 @@ class PDE:
651
649
  def pos(u): return np.maximum(u, 0.0)
652
650
  def sech(u): return 1.0 / np.cosh(u)
653
651
  def sign(u): return np.sign(u)
652
+ def step_fn(u): return np.heaviside(u, 1.0)
653
+ def heaviside_fn(u, h0=1.0): return np.heaviside(u, h0)
654
+ def clamp(u, lower, upper): return np.clip(u, lower, upper)
655
+
656
+ def elementwise_min(*values):
657
+ if not values:
658
+ raise ValueError("min requires at least one value")
659
+ out = values[0]
660
+ for value in values[1:]:
661
+ out = np.minimum(out, value)
662
+ return out
663
+
664
+ def elementwise_max(*values):
665
+ if not values:
666
+ raise ValueError("max requires at least one value")
667
+ out = values[0]
668
+ for value in values[1:]:
669
+ out = np.maximum(out, value)
670
+ return out
671
+
672
+ def piecewise(*cases):
673
+ if not cases:
674
+ raise ValueError("Piecewise requires at least one case")
675
+
676
+ # Also support numpy-style piecewise(cond, if_true, if_false).
677
+ if len(cases) == 3 and not isinstance(cases[0], (tuple, list)):
678
+ cond, if_true, if_false = cases
679
+ return np.where(cond, if_true, if_false)
680
+
681
+ condlist = []
682
+ choicelist = []
683
+ default = 0.0
684
+
685
+ for case in cases:
686
+ if not isinstance(case, (tuple, list)) or len(case) != 2:
687
+ raise ValueError("Piecewise cases must be (expr, condition) pairs")
688
+ expr, cond = case
689
+ if isinstance(cond, (bool, np.bool_)):
690
+ if bool(cond):
691
+ default = expr
692
+ continue
693
+ condlist.append(cond)
694
+ choicelist.append(expr)
695
+
696
+ if not condlist:
697
+ return np.asarray(default)
698
+ return np.select(condlist, choicelist, default=default)
654
699
 
655
700
  env = {
656
701
  "np": np,
@@ -666,6 +711,12 @@ class PDE:
666
711
  "gradmag": gradmag, "gradl1": gradl1,
667
712
  "grad": grad, "div": div, "advect": advect,
668
713
  "pos": pos, "sech": sech, "sign": sign,
714
+ "step": step_fn, "heaviside": heaviside_fn, "Heaviside": heaviside_fn,
715
+ "clamp": clamp, "clip": np.clip,
716
+ "where": np.where,
717
+ "min": elementwise_min, "max": elementwise_max,
718
+ "minimum": np.minimum, "maximum": np.maximum,
719
+ "piecewise": piecewise, "Piecewise": piecewise,
669
720
  }
670
721
 
671
722
  env.update(self.external_variables)
@@ -377,13 +377,17 @@ def normalize_symbols(expr: str) -> str:
377
377
  expr = re.sub(r"(\))\s*([a-zA-Z_])", r"\1*\2", expr)
378
378
  # Symbol followed by operator function: eta lap(u) -> eta*lap(u)
379
379
  expr = re.sub(
380
- r"([a-zA-Z_][a-zA-Z0-9_]*)\s*(lap|grad|div|dx|dz|dxx|dzz|pos|gradl1|gradmag)\s*\(",
380
+ r"([a-zA-Z_][a-zA-Z0-9_]*)\s*(lap|grad|div|dx|dz|dxx|dzz|pos|gradl1|gradmag|clamp|clip|piecewise|Piecewise|min|max|minimum|maximum|where)\s*\(",
381
381
  r"\1*\2(",
382
382
  expr,
383
383
  )
384
384
 
385
385
  # Fix accidental insertions like gradl1*(u) -> gradl1(u)
386
- expr = re.sub(r"\b(gradl1|gradmag|lap|dx|dz|dxx|dzz|grad|div|pos)\s*\*\s*\(", r"\1(", expr)
386
+ expr = re.sub(
387
+ r"\b(gradl1|gradmag|lap|dx|dz|dxx|dzz|grad|div|pos|clamp|clip|piecewise|Piecewise|min|max|minimum|maximum|where)\s*\*\s*\(",
388
+ r"\1(",
389
+ expr,
390
+ )
387
391
  # Avoid inserting * for known functions
388
392
  def _fn_mul(match):
389
393
  name = match.group(1)
@@ -395,6 +399,10 @@ def normalize_symbols(expr: str) -> str:
395
399
  "log", "log10", "log2",
396
400
  "abs", "sech", "sign", "step", "heaviside",
397
401
  "lap", "dx", "dz", "dxx", "dzz", "grad", "div", "advect", "gradmag", "gradl1", "pos",
402
+ "clamp", "clip", "where",
403
+ "piecewise", "Piecewise",
404
+ "min", "max", "minimum", "maximum",
405
+ "Heaviside",
398
406
  }:
399
407
  return f"{name}("
400
408
  return f"{name}*("
File without changes
File without changes