OGRePy 1.2.0__tar.gz → 1.3.1__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
  MIT License
2
2
 
3
- Copyright (c) 2024 Barak Shoshany
3
+ Copyright (c) 2025 Barak Shoshany
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  r"""
2
2
  # OGRePy: An Object-Oriented General Relativity Package for Python
3
- v1.2.0 (2024-09-15)
3
+ v1.3.1 (2025-08-03)
4
4
 
5
5
  By **Barak Shoshany**\
6
6
  Email: <baraksh@gmail.com>\
@@ -12,24 +12,29 @@ PyPi project: <https://pypi.org/project/OGRePy/>
12
12
 
13
13
  Based on the Mathematica package [OGRe](https://github.com/bshoshany/OGRe) by Barak Shoshany.
14
14
 
15
- Copyright (c) 2024 [Barak Shoshany](https://baraksh.com/). Licensed under the [MIT license](https://github.com/bshoshany/OGRePy/blob/master/LICENSE.txt).
15
+ Copyright (c) 2025 [Barak Shoshany](https://baraksh.com/). Licensed under the [MIT license](https://github.com/bshoshany/OGRePy/blob/master/LICENSE.txt).
16
16
 
17
17
  If you use this package in software of any kind, please provide a link to [the GitHub repository](https://github.com/bshoshany/OGRePy) in the source code and documentation.
18
18
 
19
19
  If you use this package in published research, please cite it as follows:
20
20
 
21
- * Barak Shoshany, *"OGRePy: An Object-Oriented General Relativity Package for Python"*, [doi:10.48550/arXiv.2409.03803](https://doi.org/10.48550/arXiv.2409.03803), [arXiv:2409.03803](https://arxiv.org/abs/2409.03803) (September 2024)
21
+ * Barak Shoshany, *"OGRePy: An Object-Oriented General Relativity Package for Python"*, [Journal of Open Research Software 13: 9](https://openresearchsoftware.metajnl.com/articles/10.5334/jors.558), [doi:10.5334/jors.558](https://doi.org/10.5334/jors.558), [arXiv:2409.03803](https://arxiv.org/abs/2409.03803) (July 2025)
22
22
 
23
23
  You can use the following BibTeX entry:
24
24
 
25
25
  ```bibtex
26
- @article{Shoshany2024_OGRePy,
26
+ @article{ShoshanyOGRePy,
27
27
  archiveprefix = {arXiv},
28
28
  author = {Barak Shoshany},
29
- doi = {10.48550/arXiv.2409.03803},
29
+ doi = {10.5334/jors.558},
30
30
  eprint = {2409.03803},
31
+ issn = {2049-9647},
32
+ journal = {Journal of Open Research Software},
33
+ pages = {9},
34
+ publisher = {Ubiquity Press, Ltd.},
31
35
  title = {{OGRePy: An Object-Oriented General Relativity Package for Python}},
32
- year = {2024}
36
+ volume = {13},
37
+ year = {2025},
33
38
  }
34
39
  ```
35
40
 
@@ -43,4 +48,4 @@ import sympy as s
43
48
  from ._core import Coordinates, CovariantD, Metric, OGRePyError, PartialD, Tensor, __version__, calc, cite, compare, diag, doc, func, info, options, release_date, sym, syms, update_check, welcome
44
49
 
45
50
  # The names that will be exported if using `from OGRePy import *`. Contains exactly all the names imported above.
46
- __all__: list[str] = ["s", "Coordinates", "CovariantD", "Metric", "OGRePyError", "PartialD", "Tensor", "__version__", "calc", "cite", "compare", "diag", "doc", "func", "info", "options", "release_date", "sym", "syms", "update_check", "welcome"]
51
+ __all__: list[str] = ["Coordinates", "CovariantD", "Metric", "OGRePyError", "PartialD", "Tensor", "__version__", "calc", "cite", "compare", "diag", "doc", "func", "info", "options", "release_date", "s", "sym", "syms", "update_check", "welcome"]
@@ -1,6 +1,6 @@
1
1
  r"""
2
2
  # OGRePy: An Object-Oriented General Relativity Package for Python
3
- v1.2.0 (2024-09-15)
3
+ v1.3.1 (2025-08-03)
4
4
 
5
5
  By **Barak Shoshany**\
6
6
  Email: <baraksh@gmail.com>\
@@ -12,24 +12,29 @@ PyPi project: <https://pypi.org/project/OGRePy/>
12
12
 
13
13
  Based on the Mathematica package [OGRe](https://github.com/bshoshany/OGRe) by Barak Shoshany.
14
14
 
15
- Copyright (c) 2024 [Barak Shoshany](https://baraksh.com/). Licensed under the [MIT license](https://github.com/bshoshany/OGRePy/blob/master/LICENSE.txt).
15
+ Copyright (c) 2025 [Barak Shoshany](https://baraksh.com/). Licensed under the [MIT license](https://github.com/bshoshany/OGRePy/blob/master/LICENSE.txt).
16
16
 
17
17
  If you use this package in software of any kind, please provide a link to [the GitHub repository](https://github.com/bshoshany/OGRePy) in the source code and documentation.
18
18
 
19
19
  If you use this package in published research, please cite it as follows:
20
20
 
21
- * Barak Shoshany, *"OGRePy: An Object-Oriented General Relativity Package for Python"*, [doi:10.48550/arXiv.2409.03803](https://doi.org/10.48550/arXiv.2409.03803), [arXiv:2409.03803](https://arxiv.org/abs/2409.03803) (September 2024)
21
+ * Barak Shoshany, *"OGRePy: An Object-Oriented General Relativity Package for Python"*, [Journal of Open Research Software 13: 9](https://openresearchsoftware.metajnl.com/articles/10.5334/jors.558), [doi:10.5334/jors.558](https://doi.org/10.5334/jors.558), [arXiv:2409.03803](https://arxiv.org/abs/2409.03803) (July 2025)
22
22
 
23
23
  You can use the following BibTeX entry:
24
24
 
25
25
  ```bibtex
26
- @article{Shoshany2024_OGRePy,
26
+ @article{ShoshanyOGRePy,
27
27
  archiveprefix = {arXiv},
28
28
  author = {Barak Shoshany},
29
- doi = {10.48550/arXiv.2409.03803},
29
+ doi = {10.5334/jors.558},
30
30
  eprint = {2409.03803},
31
+ issn = {2049-9647},
32
+ journal = {Journal of Open Research Software},
33
+ pages = {9},
34
+ publisher = {Ubiquity Press, Ltd.},
31
35
  title = {{OGRePy: An Object-Oriented General Relativity Package for Python}},
32
- year = {2024}
36
+ volume = {13},
37
+ year = {2025},
33
38
  }
34
39
  ```
35
40
 
@@ -56,6 +61,7 @@ import inspect
56
61
  import itertools
57
62
  import json
58
63
  import os
64
+ import pathlib
59
65
  import re
60
66
  import sys
61
67
  import urllib.request
@@ -106,8 +112,8 @@ class OGRePyError(Exception):
106
112
  ####################
107
113
 
108
114
 
109
- __version__: str = "1.2.0"
110
- release_date: str = "2024-09-15"
115
+ __version__: str = "1.3.1"
116
+ release_date: str = "2025-08-03"
111
117
 
112
118
 
113
119
  ####################
@@ -148,18 +154,21 @@ def cite() -> None:
148
154
  inspect.cleandoc("""
149
155
  If you use this package in published research, please cite it as follows:
150
156
 
151
- * Barak Shoshany, *"OGRePy: An Object-Oriented General Relativity Package for Python"*, [doi:10.48550/arXiv.2409.03803](https://doi.org/10.48550/arXiv.2409.03803), [arXiv:2409.03803](https://arxiv.org/abs/2409.03803) (September 2024)
157
+ * Barak Shoshany, *"OGRePy: An Object-Oriented General Relativity Package for Python"*, [Journal of Open Research Software 13: 9](https://openresearchsoftware.metajnl.com/articles/10.5334/jors.558), [doi:10.5334/jors.558](https://doi.org/10.5334/jors.558), [arXiv:2409.03803](https://arxiv.org/abs/2409.03803) (July 2025)
152
158
 
153
159
  You can use the following BibTeX entry:
154
160
 
155
161
  ```bibtex
156
- @article{Shoshany2024_OGRePy,
157
- archiveprefix = {arXiv},
158
- author = {Barak Shoshany},
159
- doi = {10.48550/arXiv.2409.03803},
160
- eprint = {2409.03803},
161
- title = {{OGRePy: An Object-Oriented General Relativity Package for Python}},
162
- year = {2024}
162
+ @article{Shoshany2025_OGRePy,
163
+ author = {Barak Shoshany},
164
+ doi = {10.5334/jors.558},
165
+ issn = {2049-9647},
166
+ journal = {Journal of Open Research Software},
167
+ publisher = {Ubiquity Press, Ltd.},
168
+ title = {{OGRePy: An Object-Oriented General Relativity Package for Python}},
169
+ url = {http://dx.doi.org/10.5334/jors.558},
170
+ volume = {13},
171
+ year = {2025},
163
172
  }
164
173
  ```
165
174
 
@@ -250,11 +259,11 @@ def doc(
250
259
  try:
251
260
  signature: str = str(inspect.signature(obj)).replace("'", "")
252
261
  except (ValueError, TypeError):
253
- _handle_error(f"Could not find call signature for `{name if name != "" else "this object"}`.")
262
+ _handle_error(f"Could not find call signature for `{name if name != '' else 'this object'}`.")
254
263
  # Print the object's name, then its signature, then the docstring itself. Types in the signature will be surrounded by single quotes, so we eliminate those before printing.
255
264
  _display_markdown('<div style="border: 1px solid; margin: auto; padding: 1em; width: 90%">\n\n`' + name + signature + "`\n\n" + docs + "\n\n</div>")
256
265
  else:
257
- _handle_error(f"Could not find documentation for `{name if name != "" else "this object"}`.")
266
+ _handle_error(f"Could not find documentation for `{name if name != '' else 'this object'}`.")
258
267
 
259
268
 
260
269
  def func(
@@ -377,24 +386,50 @@ def welcome() -> None:
377
386
  """
378
387
  Print the welcome message.
379
388
  """
380
- with importlib.resources.as_file(importlib.resources.files().joinpath("docs/OGRePy_Documentation")) as file:
381
- # Create links to the bundled documentation files.
382
- ipynb_link: str = f"""<a href="{file.with_suffix(".ipynb").as_posix()}">.ipynb</a>"""
383
- pdf_link: str = f"""<a href="{file.with_suffix(".pdf").as_posix()}">.pdf</a>"""
384
- html_link: str = f"""<a href="#" onclick="window.open('{file.with_suffix(".html").as_uri()}', '_blank')">.html</a>"""
385
- # Display the welcome message.
386
- _display_markdown(
387
- inspect.cleandoc(rf"""
388
- **OGRePy: An <u>O</u>bject-Oriented <u>G</u>eneral <u>Re</u>lativity Package for <u>Py</u>thon\
389
- By [Barak Shoshany](https://github.com/bshoshany) ([baraksh@gmail.com](mailto:baraksh@gmail.com)) ([baraksh.com](https://baraksh.com/))\
390
- v{__version__} ({release_date})\
391
- GitHub repository: <https://github.com/bshoshany/OGRePy>\
392
- Documentation: {ipynb_link}, {pdf_link}, {html_link}**
393
- """),
394
- )
395
- # If we're not in a notebook, warn that the package should be used inside a notebook.
396
- if not _in_notebook:
397
- _display_markdown("WARNING: No notebook interface detected! This package was designed to be used inside a Jupyter notebook in Visual Studio Code or JupyterLab.")
389
+ ipynb_filename: str = ""
390
+ pdf_filename: str = ""
391
+ html_filename: str = ""
392
+ if "pyodide" not in sys.modules:
393
+ # The documentation files are bundled with the package so that they can be viewed offline. We make sure they exist and get their paths.
394
+ with importlib.resources.as_file(importlib.resources.files().joinpath("docs/OGRePy_Documentation")) as file:
395
+ ipynb_file: pathlib.Path = file.with_suffix(".ipynb")
396
+ if ipynb_file.exists():
397
+ ipynb_filename = ipynb_file.as_posix()
398
+ pdf_file: pathlib.Path = file.with_suffix(".pdf")
399
+ if pdf_file.exists():
400
+ pdf_filename = pdf_file.as_posix()
401
+ html_file: pathlib.Path = file.with_suffix(".html")
402
+ if html_file.exists():
403
+ html_filename = html_file.as_uri()
404
+ else:
405
+ # If we're running in a JupyterLite notebook, we will not be able to access the documentation files directly. However, if using OGRePyLive, the files should be available in the same folder as the notebook, so we check for that.
406
+ file = pathlib.Path("./OGRePy_Documentation")
407
+ ipynb_file: pathlib.Path = file.with_suffix(".ipynb")
408
+ if ipynb_file.exists():
409
+ ipynb_filename = ipynb_file.as_posix()
410
+ pdf_file: pathlib.Path = file.with_suffix(".pdf")
411
+ if pdf_file.exists():
412
+ pdf_filename = pdf_file.as_posix()
413
+ html_file: pathlib.Path = file.with_suffix(".html")
414
+ if html_file.exists():
415
+ html_filename = html_file.as_posix()
416
+ ipynb_link: str = f"""<a href="{ipynb_filename if ipynb_filename != "" else "https://github.com/bshoshany/OGRePy/blob/master/OGRePy/docs/OGRePy_Documentation.ipynb"}">.ipynb</a>"""
417
+ pdf_link: str = f"""<a href="{pdf_filename if pdf_filename != "" else "https://github.com/bshoshany/OGRePy/blob/master/OGRePy/docs/OGRePy_Documentation.pdf"}">.pdf</a>"""
418
+ html_url: str = html_filename if html_filename != "" else "https://raw.githack.com/bshoshany/OGRePy/master/OGRePy/docs/OGRePy_Documentation.html"
419
+ html_link: str = f"""<a href="#" onclick="window.open('{html_url}', '_blank')">.html</a>""" if "pyodide" not in sys.modules else f"""<a href="{html_url}">.html</a>"""
420
+ # Display the welcome message.
421
+ _display_markdown(
422
+ inspect.cleandoc(rf"""
423
+ **OGRePy: An <u>O</u>bject-Oriented <u>G</u>eneral <u>Re</u>lativity Package for <u>Py</u>thon\
424
+ By [Barak Shoshany](https://github.com/bshoshany) ([baraksh@gmail.com](mailto:baraksh@gmail.com)) ([baraksh.com](https://baraksh.com/))\
425
+ v{__version__} ({release_date})\
426
+ GitHub repository: <https://github.com/bshoshany/OGRePy>\
427
+ Documentation: {ipynb_link}, {pdf_link}, {html_link}**
428
+ """),
429
+ )
430
+ # If we're not in a notebook, warn that the package should be used inside a notebook.
431
+ if not _in_notebook:
432
+ _display_markdown("WARNING: No notebook interface detected! This package was designed to be used inside a Jupyter notebook in Visual Studio Code or JupyterLab.")
398
433
 
399
434
 
400
435
  ##################
@@ -537,7 +572,7 @@ class Coordinates:
537
572
  text: str = f"* **Name**: {_lookup_names_string(self)}\n"
538
573
  text += f"* **Class**: {type(self).__name__}\n"
539
574
  text += f"* **Dimensions**: {self.dim()}\n"
540
- text += f"* **Default Coordinates For**: {", ".join(_using_coords(self))}\n"
575
+ text += f"* **Default Coordinates For**: {', '.join(_using_coords(self))}\n"
541
576
  _display_markdown(text)
542
577
 
543
578
  def inverse_jacobian(
@@ -837,7 +872,7 @@ class CovariantD:
837
872
  dummy_christoffel: str = _to_tex(s.Dummy())
838
873
  for pos, (index_letter, index_type) in enumerate(zip(tensor_letters, use_indices, strict=True)):
839
874
  # Replace the current index with the dummy index in the tensor's index specification.
840
- index_replaced: IndexSpecification = tensor_letters[0:pos] + [dummy_christoffel] + tensor_letters[pos + 1 : rank]
875
+ index_replaced: IndexSpecification = [*tensor_letters[0:pos], dummy_christoffel, *tensor_letters[pos + 1 : rank]]
841
876
  if index_type == 1:
842
877
  # For an upper index, add the Christoffel symbols with their third index contracted with the tensor.
843
878
  out_tensor += other(*index_replaced) @ use_christoffel(index_letter, dummy_covariant, dummy_christoffel)
@@ -1038,7 +1073,7 @@ class Tensor:
1038
1073
  output_tensor._symbol = self._symbol + sign + other_symbol
1039
1074
  return output_tensor
1040
1075
 
1041
- def __call__(
1076
+ def __call__( # noqa: RET503
1042
1077
  self: Self,
1043
1078
  *letters: str | s.Symbol,
1044
1079
  ) -> Self | Tensor:
@@ -1053,14 +1088,14 @@ class Tensor:
1053
1088
  calc_letters: IndexSpecification = _collect_tex_symbols(*letters)
1054
1089
  # Check that the index specification matches the rank of the tensor.
1055
1090
  if len(calc_letters) != self.rank():
1056
- _handle_error(f"{f"The index specification\n${"".join(calc_letters)}$\n" if len(calc_letters) > 0 else "The empty index specification "}does not match the rank of the tensor. The number of indices should be {self.rank()}.")
1091
+ _handle_error(f"{f'The index specification\n${"".join(calc_letters)}$\n' if len(calc_letters) > 0 else 'The empty index specification '}does not match the rank of the tensor. The number of indices should be {self.rank()}.")
1057
1092
  # Check for duplicate index letters.
1058
1093
  tally: dict[str, int] = {letter: calc_letters.count(letter) for letter in set(calc_letters)}
1059
1094
  max_duplicates: int = max(tally.values()) if len(tally) > 0 else 0
1060
1095
  if max_duplicates > 2:
1061
1096
  # We can't have more than 2 instances of the same index.
1062
1097
  invalid_indices: IndexSpecification = [letter for letter, count in tally.items() if count > 2]
1063
- _handle_error(f"The index specification\n${"".join(calc_letters)}$\nis invalid, as it contains more than two instances of the {"index" if len(invalid_indices) == 1 else "indices"} \n${", ".join(invalid_indices)}$\n.") # noqa: RET503
1098
+ _handle_error(f"The index specification\n${''.join(calc_letters)}$\nis invalid, as it contains more than two instances of the {'index' if len(invalid_indices) == 1 else 'indices'} \n${', '.join(invalid_indices)}$\n.")
1064
1099
  elif max_duplicates == 2:
1065
1100
  # If any indices appear exactly twice, we need to trace them.
1066
1101
  trace_letters: IndexSpecification = [letter for letter, count in tally.items() if count == 2]
@@ -1161,7 +1196,7 @@ class Tensor:
1161
1196
  # Count how many summation indices there already are in the first tensor's symbol (from previous contractions).
1162
1197
  contract_count: int = len(_unique_summation_placeholders(self._symbol))
1163
1198
  # Increase the numbering of the summation indices as well.
1164
- second_symbol = _summation_pattern.sub(lambda match: f"[{match[1] if match[1] else ""}{int(match[2]) + contract_count}{match[3] if match[3] else ""}]", second_symbol)
1199
+ second_symbol = _summation_pattern.sub(lambda match: f"[{match[1] if match[1] else ''}{int(match[2]) + contract_count}{match[3] if match[3] else ''}]", second_symbol)
1165
1200
  # Add the number of contractions in the second symbol to the total count.
1166
1201
  contract_count += len(_unique_summation_placeholders(second_symbol))
1167
1202
  # Join the symbols of the two tensors to create a new symbol for the output tensor, with consecutive summation index placeholders.
@@ -1420,7 +1455,7 @@ class Tensor:
1420
1455
  if coords is None:
1421
1456
  warning = f"Using default coordinate system {_lookup_names_string(use_coords)}"
1422
1457
  if indices is None:
1423
- warning += f"{"Using" if warning == "" else " and"} default index configuration {use_indices}"
1458
+ warning += f"{'Using' if warning == '' else ' and'} default index configuration {use_indices}"
1424
1459
  if warning != "":
1425
1460
  _display_markdown(f"**OGRePy**: {warning}.")
1426
1461
  # Retrieve the components, calculating them if they have not already been calculated.
@@ -1469,7 +1504,7 @@ class Tensor:
1469
1504
  text += f"* **Metric**: {_lookup_names_string(self._metric)}\n"
1470
1505
  else:
1471
1506
  used_by: list[str] = _using_metric(self)
1472
- text += f"* **Associated Metric For**: {", ".join(used_by) if len(used_by) > 0 else "None"}\n"
1507
+ text += f"* **Associated Metric For**: {', '.join(used_by) if len(used_by) > 0 else 'None'}\n"
1473
1508
  _display_markdown(text)
1474
1509
 
1475
1510
  def list(
@@ -1715,7 +1750,7 @@ class Tensor:
1715
1750
  out: str = r"\begin{align*}" + "\n"
1716
1751
  # Create an equation for each unique element. Note that in an {align*} environment, \\ indicates the end of a line and & indicates the position where the equations will be vertically aligned.
1717
1752
  for key, value in non_zero.items():
1718
- out += rf" {" = ".join(value)} &= {_to_tex(key)} \\" + "\n"
1753
+ out += rf" {' = '.join(value)} &= {_to_tex(key)} \\" + "\n"
1719
1754
  # Remove the last end of line indicator (otherwise there will be an extra empty line) and end the {align*} environment, then return the generated TeX code.
1720
1755
  return out[:-3] + "\n" + r"\end{align*}"
1721
1756
 
@@ -1831,9 +1866,10 @@ class Tensor:
1831
1866
  # Replace any derivative with respect to the curve parameter with a more compact partial derivative symbol, and enclose the argument in parentheses (which SymPy doesn't do for some reason).
1832
1867
  components = _array_subs(components, {f: sym(r"\partial_{" + _to_tex(param) + r"} \left(" + _to_tex(f.args[0]) + r"\right)") for f in components.atoms(s.Derivative) if f.args[1] == (param, 1)})
1833
1868
  # For all tensors, replace any derivative with respect to the coordinate symbols with a more compact partial derivative symbol.
1834
- for x in coord_symbols:
1835
- components = _array_subs(components, {f: sym(r"\partial_{" + _to_tex(x) + r"} " + _to_tex(f.args[0])) for f in components.atoms(s.Derivative) if f.args[1] == (x, 1)})
1836
- return components
1869
+ return _array_subs(
1870
+ components,
1871
+ {f: sym(r"\partial_{" + _to_tex(x) + ((r"^{" + str(f.args[1][1]) + r"}") if f.args[1][1] > 1 else "") + r"} " + _to_tex(f.args[0])) for x in coord_symbols for f in components.atoms(s.Derivative) if f.args[1][0] == x and f.args[1][1] > 0},
1872
+ )
1837
1873
 
1838
1874
  def _get_components(
1839
1875
  self: Self,
@@ -2261,7 +2297,7 @@ class Einstein(Tensor, FixedDefaultIndices):
2261
2297
  #### Returns:
2262
2298
  The calculated components.
2263
2299
  """
2264
- return (metric.ricci_tensor("mu nu") - s.Rational(1, 2) * metric.ricci_scalar() @ metric("mu nu"))._calc_representation(indices=(-1, -1), coords=coords)
2300
+ return cast(s.Array, (metric.ricci_tensor("mu nu") - s.Rational(1, 2) * metric.ricci_scalar() @ metric("mu nu"))._calc_representation(indices=(-1, -1), coords=coords))
2265
2301
 
2266
2302
 
2267
2303
  class GeodesicFromChristoffel(Tensor, CleanupCurveParameter, FixedDefaultIndices):
@@ -2476,9 +2512,9 @@ class GeodesicTimeParam(Tensor, CleanupTimeParameter, FixedDefaultIndices):
2476
2512
  # Use the first coordinate as the curve parameter.
2477
2513
  time: s.Symbol = cast(s.Symbol, coords.components()[0])
2478
2514
  # Create a tangent vector whose components are the first derivatives of the coordinates with respect to t (thus the first component will be equal to 1).
2479
- tangent: s.Array = s.Array([1] + coords.of_param_dot(time)[1:])
2515
+ tangent: s.Array = s.Array([1, *coords.of_param_dot(time)[1:]])
2480
2516
  # Create an acceleration vector whose components are the second derivatives of the coordinates with respect to t (thus the first component will be equal to 0).
2481
- accel: s.Array = s.Array([0] + coords.of_param_ddot(time)[1:])
2517
+ accel: s.Array = s.Array([0, *coords.of_param_ddot(time)[1:]])
2482
2518
  # Obtain the Christoffel symbols, and replace any instance of the spatial coordinate symbols with coordinate functions of time.
2483
2519
  param_dict: dict[s.Symbol, AppliedUndef] = coords.of_param_dict(time)
2484
2520
  del param_dict[time]
@@ -3389,7 +3425,7 @@ def _list_aliases(
3389
3425
  return f"`{names[0]}`"
3390
3426
  if len(names) == 2:
3391
3427
  return f"`{names[0]}` (alias: `{names[1]}`)"
3392
- return f"`{names[0]}` (aliases: `{"`, `".join(names[1:])}`)"
3428
+ return f"`{names[0]}` (aliases: `{'`, `'.join(names[1:])}`)"
3393
3429
 
3394
3430
 
3395
3431
  def _list_references(
@@ -3412,11 +3448,11 @@ def _list_references(
3412
3448
  if isinstance(ref, Coordinates):
3413
3449
  using: list[str] = _using_coords(ref)
3414
3450
  if len(using) > 0:
3415
- text += f", default for: `{"`, `".join(using)}`"
3451
+ text += f", default for: `{'`, `'.join(using)}`"
3416
3452
  elif isinstance(ref, Metric):
3417
3453
  using: list[str] = _using_metric(ref)
3418
3454
  if len(using) > 0:
3419
- text += f", used by: `{"`, `".join(using)}`"
3455
+ text += f", used by: `{'`, `'.join(using)}`"
3420
3456
  text += "\n"
3421
3457
  return text
3422
3458
 
@@ -3624,7 +3660,7 @@ def _using_object(
3624
3660
  # Create a list of relevant tensors, including aliases if any.
3625
3661
  using: list[str] = []
3626
3662
  for tensor, names in tensor_reverse.items():
3627
- if isinstance(obj, Coordinates) and obj is tensor.default_coords or isinstance(obj, Metric) and obj is tensor.metric():
3663
+ if (isinstance(obj, Coordinates) and obj is tensor.default_coords) or (isinstance(obj, Metric) and obj is tensor.metric()):
3628
3664
  using.append(_list_aliases(names))
3629
3665
  return using
3630
3666
 
@@ -3734,13 +3770,13 @@ def _validate_permutation(
3734
3770
  letters: IndexSpecification = _collect_tex_symbols(*index_spec)
3735
3771
  # Check that the index specifications matches the rank of the tensor.
3736
3772
  if len(letters) != rank:
3737
- _handle_error(f"The index specification\n${"".join(letters)}$\ndoes not match the rank of the tensor. The number of indices should be {rank}.")
3773
+ _handle_error(f"The index specification\n${''.join(letters)}$\ndoes not match the rank of the tensor. The number of indices should be {rank}.")
3738
3774
  # Check for duplicate index letters.
3739
3775
  tally: dict[str, int] = {letter: letters.count(letter) for letter in set(letters)}
3740
3776
  max_duplicates: int = max(tally.values()) if len(tally) > 0 else 0
3741
3777
  if max_duplicates > 1:
3742
3778
  invalid_indices: IndexSpecification = [letter for letter, count in tally.items() if count > 1]
3743
- _handle_error(f"The index specification\n${"".join(letters)}$\nis invalid, as it contains more than one instance of the {"index" if len(invalid_indices) == 1 else "indices"} \n${", ".join(invalid_indices)}$\n.")
3779
+ _handle_error(f"The index specification\n${''.join(letters)}$\nis invalid, as it contains more than one instance of the {'index' if len(invalid_indices) == 1 else 'indices'} \n${', '.join(invalid_indices)}$\n.")
3744
3780
  # Return the letters as a list of TeX strings.
3745
3781
  return letters
3746
3782
 
@@ -1,6 +1,6 @@
1
1
  r"""
2
2
  # OGRePy: An Object-Oriented General Relativity Package for Python
3
- v1.2.0 (2024-09-15)
3
+ v1.3.1 (2025-08-03)
4
4
 
5
5
  By **Barak Shoshany**\
6
6
  Email: <baraksh@gmail.com>\
@@ -12,24 +12,29 @@ PyPi project: <https://pypi.org/project/OGRePy/>
12
12
 
13
13
  Based on the Mathematica package [OGRe](https://github.com/bshoshany/OGRe) by Barak Shoshany.
14
14
 
15
- Copyright (c) 2024 [Barak Shoshany](https://baraksh.com/). Licensed under the [MIT license](https://github.com/bshoshany/OGRePy/blob/master/LICENSE.txt).
15
+ Copyright (c) 2025 [Barak Shoshany](https://baraksh.com/). Licensed under the [MIT license](https://github.com/bshoshany/OGRePy/blob/master/LICENSE.txt).
16
16
 
17
17
  If you use this package in software of any kind, please provide a link to [the GitHub repository](https://github.com/bshoshany/OGRePy) in the source code and documentation.
18
18
 
19
19
  If you use this package in published research, please cite it as follows:
20
20
 
21
- * Barak Shoshany, *"OGRePy: An Object-Oriented General Relativity Package for Python"*, [doi:10.48550/arXiv.2409.03803](https://doi.org/10.48550/arXiv.2409.03803), [arXiv:2409.03803](https://arxiv.org/abs/2409.03803) (September 2024)
21
+ * Barak Shoshany, *"OGRePy: An Object-Oriented General Relativity Package for Python"*, [Journal of Open Research Software 13: 9](https://openresearchsoftware.metajnl.com/articles/10.5334/jors.558), [doi:10.5334/jors.558](https://doi.org/10.5334/jors.558), [arXiv:2409.03803](https://arxiv.org/abs/2409.03803) (July 2025)
22
22
 
23
23
  You can use the following BibTeX entry:
24
24
 
25
25
  ```bibtex
26
- @article{Shoshany2024_OGRePy,
26
+ @article{ShoshanyOGRePy,
27
27
  archiveprefix = {arXiv},
28
28
  author = {Barak Shoshany},
29
- doi = {10.48550/arXiv.2409.03803},
29
+ doi = {10.5334/jors.558},
30
30
  eprint = {2409.03803},
31
+ issn = {2049-9647},
32
+ journal = {Journal of Open Research Software},
33
+ pages = {9},
34
+ publisher = {Ubiquity Press, Ltd.},
31
35
  title = {{OGRePy: An Object-Oriented General Relativity Package for Python}},
32
- year = {2024}
36
+ volume = {13},
37
+ year = {2025},
33
38
  }
34
39
  ```
35
40