OGRePy 1.0.1__tar.gz → 1.1.0__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.
- {ogrepy-1.0.1 → ogrepy-1.1.0}/OGRePy/__init__.py +3 -8
- {ogrepy-1.0.1 → ogrepy-1.1.0}/OGRePy/_core.py +162 -70
- {ogrepy-1.0.1 → ogrepy-1.1.0}/OGRePy/abc.py +1 -0
- ogrepy-1.1.0/OGRePy/docs/OGRePy_Documentation.html +14450 -0
- ogrepy-1.1.0/OGRePy/docs/OGRePy_Documentation.ipynb +7561 -0
- ogrepy-1.1.0/OGRePy/docs/OGRePy_Documentation.pdf +0 -0
- {ogrepy-1.0.1 → ogrepy-1.1.0}/OGRePy.egg-info/PKG-INFO +178 -101
- {ogrepy-1.0.1 → ogrepy-1.1.0}/OGRePy.egg-info/SOURCES.txt +4 -1
- ogrepy-1.1.0/OGRePy.egg-info/requires.txt +2 -0
- {ogrepy-1.0.1 → ogrepy-1.1.0}/PKG-INFO +178 -101
- {ogrepy-1.0.1 → ogrepy-1.1.0}/README.md +175 -98
- {ogrepy-1.0.1 → ogrepy-1.1.0}/pyproject.toml +10 -6
- ogrepy-1.0.1/OGRePy.egg-info/requires.txt +0 -2
- {ogrepy-1.0.1 → ogrepy-1.1.0}/LICENSE.txt +0 -0
- {ogrepy-1.0.1 → ogrepy-1.1.0}/OGRePy/py.typed +0 -0
- {ogrepy-1.0.1 → ogrepy-1.1.0}/OGRePy.egg-info/dependency_links.txt +0 -0
- {ogrepy-1.0.1 → ogrepy-1.1.0}/OGRePy.egg-info/top_level.txt +0 -0
- {ogrepy-1.0.1 → ogrepy-1.1.0}/setup.cfg +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
r"""
|
|
2
2
|
# OGRePy: An Object-Oriented General Relativity Package for Python
|
|
3
|
+
v1.1.0 (2024-09-08)
|
|
3
4
|
|
|
4
5
|
By **Barak Shoshany**\
|
|
5
6
|
Email: <baraksh@gmail.com>\
|
|
@@ -20,13 +21,7 @@ If you use this package in published software or research, please provide a link
|
|
|
20
21
|
import sympy as s
|
|
21
22
|
|
|
22
23
|
# Import all public OGRePy objects.
|
|
23
|
-
from ._core import Coordinates, CovariantD, Metric, OGRePyError, PartialD, Tensor, __version__, calc, diag, doc, func, info, options, release_date, sym, syms, welcome
|
|
24
|
+
from ._core import Coordinates, CovariantD, Metric, OGRePyError, PartialD, Tensor, __version__, calc, diag, doc, func, info, options, release_date, sym, syms, update_check, welcome
|
|
24
25
|
|
|
25
26
|
# The names that will be exported if using `from OGRePy import *`. Contains exactly all the names imported above.
|
|
26
|
-
__all__: list[str] = ["s", "Coordinates", "CovariantD", "Metric", "OGRePyError", "PartialD", "Tensor", "__version__", "calc", "diag", "doc", "func", "info", "options", "release_date", "sym", "syms", "welcome"]
|
|
27
|
-
|
|
28
|
-
# Display the welcome message, but not if `OGREPY_DISABLE_WELCOME = True` was defined in the notebook before importing the package.
|
|
29
|
-
import __main__
|
|
30
|
-
|
|
31
|
-
if __main__.__dict__.get("OGREPY_DISABLE_WELCOME", False) is not True:
|
|
32
|
-
welcome()
|
|
27
|
+
__all__: list[str] = ["s", "Coordinates", "CovariantD", "Metric", "OGRePyError", "PartialD", "Tensor", "__version__", "calc", "diag", "doc", "func", "info", "options", "release_date", "sym", "syms", "update_check", "welcome"]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
r"""
|
|
2
2
|
# OGRePy: An Object-Oriented General Relativity Package for Python
|
|
3
|
+
v1.1.0 (2024-09-08)
|
|
3
4
|
|
|
4
5
|
By **Barak Shoshany**\
|
|
5
6
|
Email: <baraksh@gmail.com>\
|
|
@@ -29,10 +30,16 @@ If you use this package in published software or research, please provide a link
|
|
|
29
30
|
from __future__ import annotations
|
|
30
31
|
|
|
31
32
|
import collections
|
|
33
|
+
import concurrent.futures
|
|
32
34
|
import functools
|
|
35
|
+
import importlib.resources
|
|
33
36
|
import inspect
|
|
34
37
|
import itertools
|
|
38
|
+
import json
|
|
39
|
+
import os
|
|
35
40
|
import re
|
|
41
|
+
import sys
|
|
42
|
+
import urllib.request
|
|
36
43
|
from collections.abc import Callable, Mapping
|
|
37
44
|
from copy import copy
|
|
38
45
|
from numbers import Number
|
|
@@ -80,8 +87,8 @@ class OGRePyError(Exception):
|
|
|
80
87
|
####################
|
|
81
88
|
|
|
82
89
|
|
|
83
|
-
__version__: str = "1.0
|
|
84
|
-
release_date: str = "2024-09-
|
|
90
|
+
__version__: str = "1.1.0"
|
|
91
|
+
release_date: str = "2024-09-08"
|
|
85
92
|
|
|
86
93
|
|
|
87
94
|
####################
|
|
@@ -107,7 +114,7 @@ def calc(
|
|
|
107
114
|
# Validate the symbol.
|
|
108
115
|
symbol = _validate_symbol(symbol, formula.rank())
|
|
109
116
|
# Create a new tensor with the same properties and components as the source tensor.
|
|
110
|
-
new: Tensor = Tensor(metric=formula.metric(),
|
|
117
|
+
new: Tensor = Tensor(metric=formula.metric(), indices=formula.default_indices, coords=formula.default_coords, components=formula._get_components(indices=formula.default_indices, coords=formula.default_coords), symbol=symbol) # pyright: ignore [reportPrivateUsage]
|
|
111
118
|
# Permute the indices if necessary.
|
|
112
119
|
if permute is not None and permute != formula.index_letters():
|
|
113
120
|
new.permute(new=permute, old=formula.index_letters())
|
|
@@ -235,31 +242,68 @@ def syms(
|
|
|
235
242
|
return [sym(symbol, **assumptions) for symbol in all_symbols]
|
|
236
243
|
|
|
237
244
|
|
|
245
|
+
def update_check(
|
|
246
|
+
*,
|
|
247
|
+
quiet: bool = False,
|
|
248
|
+
) -> None:
|
|
249
|
+
"""
|
|
250
|
+
Check for package updates.
|
|
251
|
+
#### Parameters:
|
|
252
|
+
* `quiet` (optional): Whether to notify even if you have the latest version of the package (`False`, the default) or only notify if a new version of the package is available (`True`).
|
|
253
|
+
"""
|
|
254
|
+
url = "https://pypi.org/pypi/OGRePy/json"
|
|
255
|
+
try:
|
|
256
|
+
with urllib.request.urlopen(url=url, timeout=5) as response:
|
|
257
|
+
latest: str = json.loads(response.read())["info"]["version"]
|
|
258
|
+
if latest == __version__:
|
|
259
|
+
if quiet is False:
|
|
260
|
+
_display_markdown("**OGRePy**: You have the latest version of the package.")
|
|
261
|
+
else:
|
|
262
|
+
# Detect how the package was imported so we can give the user the same import statement in the message.
|
|
263
|
+
import_statement: str
|
|
264
|
+
my_names: list[str] = _lookup_names(sys.modules["OGRePy"])
|
|
265
|
+
if len(my_names) == 0:
|
|
266
|
+
import_statement = "from OGRePy import *"
|
|
267
|
+
elif my_names[0] == "OGRePy":
|
|
268
|
+
import_statement = "import OGRePy"
|
|
269
|
+
else:
|
|
270
|
+
import_statement = f"import OGRePy as {my_names[0]}"
|
|
271
|
+
_display_markdown(
|
|
272
|
+
inspect.cleandoc(f"""
|
|
273
|
+
**OGRePy**: A new version of the package is available: **v{latest}** ([view changelog](https://github.com/bshoshany/OGRePy/blob/master/CHANGELOG.md)). To update, please execute the following commands in a notebook cell:
|
|
274
|
+
```
|
|
275
|
+
%pip install --upgrade OGRePy
|
|
276
|
+
%reset --aggressive -f
|
|
277
|
+
{import_statement}
|
|
278
|
+
```
|
|
279
|
+
"""),
|
|
280
|
+
)
|
|
281
|
+
except OSError as exc:
|
|
282
|
+
_display_markdown(f"**OGRePy**: Could not check for updates automatically: `{exc}`. Please visit <https://pypi.org/project/OGRePy/> to check manually.")
|
|
283
|
+
|
|
284
|
+
|
|
238
285
|
def welcome() -> None:
|
|
239
286
|
"""
|
|
240
287
|
Print the welcome message.
|
|
241
288
|
"""
|
|
242
|
-
|
|
243
|
-
#
|
|
289
|
+
with importlib.resources.as_file(importlib.resources.files().joinpath("docs/OGRePy_Documentation")) as file:
|
|
290
|
+
# Create links to the bundled documentation files.
|
|
291
|
+
ipynb_link: str = f"""<a href="{file.with_suffix(".ipynb").as_posix()}">.ipynb</a>"""
|
|
292
|
+
pdf_link: str = f"""<a href="{file.with_suffix(".pdf").as_posix()}">.pdf</a>"""
|
|
293
|
+
html_link: str = f"""<a href="#" onclick="window.open('{file.with_suffix(".html").as_uri()}', '_blank')">.html</a>"""
|
|
294
|
+
# Display the welcome message.
|
|
244
295
|
_display_markdown(
|
|
245
296
|
inspect.cleandoc(rf"""
|
|
246
|
-
|
|
297
|
+
**OGRePy: An <u>O</u>bject-Oriented <u>G</u>eneral <u>Re</u>lativity Package for <u>Py</u>thon\
|
|
247
298
|
By [Barak Shoshany](https://github.com/bshoshany) ([baraksh@gmail.com](mailto:baraksh@gmail.com)) ([baraksh.com](https://baraksh.com/))\
|
|
248
299
|
v{__version__} ({release_date})\
|
|
249
|
-
GitHub repository: <https://github.com/bshoshany/OGRePy
|
|
250
|
-
|
|
251
|
-
)
|
|
252
|
-
else:
|
|
253
|
-
# If we're not in a notebook, display a plain text welcome message, and warn that the package should be used inside a notebook.
|
|
254
|
-
print( # noqa: T201
|
|
255
|
-
inspect.cleandoc(rf"""
|
|
256
|
-
OGRePy: An Object-Oriented General Relativity Package for Python
|
|
257
|
-
By Barak Shoshany (baraksh@gmail.com) (https://baraksh.com/)
|
|
258
|
-
v{__version__} ({release_date})
|
|
259
|
-
GitHub repository: https://github.com/bshoshany/OGRePy
|
|
260
|
-
WARNING: No notebook interface detected! This package was designed to be used inside a Jupyter notebook in Visual Studio Code or JupyterLab.
|
|
300
|
+
GitHub repository: <https://github.com/bshoshany/OGRePy>\
|
|
301
|
+
Documentation: {ipynb_link}, {pdf_link}, {html_link}**
|
|
261
302
|
"""),
|
|
262
303
|
)
|
|
304
|
+
# If we're not in a notebook, warn that the package should be used inside a notebook.
|
|
305
|
+
if not _in_notebook():
|
|
306
|
+
_display_markdown("WARNING: No notebook interface detected! This package was designed to be used inside a Jupyter notebook in Visual Studio Code or JupyterLab.")
|
|
263
307
|
|
|
264
308
|
|
|
265
309
|
##################
|
|
@@ -291,7 +335,7 @@ class Coordinates:
|
|
|
291
335
|
"""
|
|
292
336
|
Construct a new coordinate object.
|
|
293
337
|
#### Parameters:
|
|
294
|
-
* `components`: One or more strings or SymPy `Symbol` objects specifying the coordinates. Strings should be in the same format as the argument to SymPy's `symbols()`, e.g. `"t x y z"
|
|
338
|
+
* `components`: One or more strings or SymPy `Symbol` objects specifying the coordinates. Strings should be in the same format as the argument to SymPy's `symbols()`, e.g. `"t x y z"`, and it is possible to enter just one string for all the coordinates.
|
|
295
339
|
"""
|
|
296
340
|
# Validate and store the components.
|
|
297
341
|
self._components: s.Array = s.Array(_collect_symbols(*components))
|
|
@@ -315,7 +359,7 @@ class Coordinates:
|
|
|
315
359
|
The names of any notebook variables that refer to this coordinates object.
|
|
316
360
|
"""
|
|
317
361
|
# We get rid of the backticks since we just want a pure string, not a Markdown formatted string.
|
|
318
|
-
return
|
|
362
|
+
return _lookup_names_string(self).replace("`", "")
|
|
319
363
|
|
|
320
364
|
def __pos__(
|
|
321
365
|
self: Self,
|
|
@@ -399,7 +443,7 @@ class Coordinates:
|
|
|
399
443
|
"""
|
|
400
444
|
Display information about this coordinate system in human-readable form.
|
|
401
445
|
"""
|
|
402
|
-
text: str = f"* **Name**: {
|
|
446
|
+
text: str = f"* **Name**: {_lookup_names_string(self)}\n"
|
|
403
447
|
text += f"* **Class**: {type(self).__name__}\n"
|
|
404
448
|
text += f"* **Dimensions**: {self.dim()}\n"
|
|
405
449
|
text += f"* **Default Coordinates For**: {", ".join(_using_coords(self))}\n"
|
|
@@ -580,7 +624,7 @@ class Coordinates:
|
|
|
580
624
|
del self._inverse_jacobians[target]
|
|
581
625
|
del self._christoffel_jacobians[target]
|
|
582
626
|
else:
|
|
583
|
-
_handle_error(f"Cannot delete the transformation to {
|
|
627
|
+
_handle_error(f"Cannot delete the transformation to {_lookup_names_string(target)}, since no such transformation exists.")
|
|
584
628
|
else:
|
|
585
629
|
# Validate the dictionary.
|
|
586
630
|
_check_dict_type(rules, s.Symbol, s.Expr, "The rules must be a dictionary with SymPy `Symbol` objects as keys and SymPy `Expr` objects as values.")
|
|
@@ -774,7 +818,7 @@ class PartialD:
|
|
|
774
818
|
# The index configuration of the output tensor will be such that the partial derivative always has a lower index in either case.
|
|
775
819
|
out_indices: list[Literal[+1, -1]] = [-1, *use_indices]
|
|
776
820
|
# Get the tensor's components in the desired representation, adding it if it does not already exist.
|
|
777
|
-
components: s.Array = other.
|
|
821
|
+
components: s.Array = other._calc_representation(indices=tuple(use_indices), coords=use_coords) # pyright: ignore [reportPrivateUsage]
|
|
778
822
|
# Collect the coordinate symbols, since we are taking derivatives with respect to them.
|
|
779
823
|
coord_symbols: s.Array = other.default_coords.components()
|
|
780
824
|
# Take the derivative of the entire tensor with respect to each of the coordinates and then combine all the results into one higher-rank tensor.
|
|
@@ -884,7 +928,7 @@ class Tensor:
|
|
|
884
928
|
# The index configuration for the second tensor must be rearranged to correctly calculate expressions like T^a_b + T_b^a.
|
|
885
929
|
use_second_indices: IndexConfiguration = tuple(use_indices[first_letters.index(second_letters[i])] for i in range(rank))
|
|
886
930
|
# Add the representation in the appropriate coordinate system and index configuration to the second tensor if it does not already exist.
|
|
887
|
-
second_components: s.Array = other._calc_representation(
|
|
931
|
+
second_components: s.Array = other._calc_representation(indices=use_second_indices, coords=use_coords)
|
|
888
932
|
# If needed, permute the indices of the second tensor so that they are the same as the first tensor (for example, if we have S^ab + T^ba we will permute the indices of T to ab).
|
|
889
933
|
other_symbol: str = other._symbol
|
|
890
934
|
if len(first_letters) > 0 and first_letters != second_letters:
|
|
@@ -1014,7 +1058,7 @@ class Tensor:
|
|
|
1014
1058
|
# The components of the first tensor will be taken in the default index representation.
|
|
1015
1059
|
first_components: s.Array = self._get_components(indices=use_first_indices, coords=use_coords)
|
|
1016
1060
|
# The components of the second tensor will be taken in the index representation that matches the contraction, with indices raised or lowered to match the corresponding indices in the first tensor.
|
|
1017
|
-
second_components: s.Array = other._calc_representation(
|
|
1061
|
+
second_components: s.Array = other._calc_representation(indices=use_second_indices, coords=use_coords)
|
|
1018
1062
|
# Perform the contraction.
|
|
1019
1063
|
result: TensorWithIndexSpecification = _contract((first_components, [*first_letters]), (second_components, [*second_letters]))
|
|
1020
1064
|
# Simplify and store the result in a new tensor object.
|
|
@@ -1026,7 +1070,7 @@ class Tensor:
|
|
|
1026
1070
|
# Count how many summation indices there already are in the first tensor's symbol (from previous contractions).
|
|
1027
1071
|
contract_count: int = len(_unique_summation_placeholders(self._symbol))
|
|
1028
1072
|
# Increase the numbering of the summation indices as well.
|
|
1029
|
-
second_symbol = _summation_pattern.sub(lambda match: f"[{match[
|
|
1073
|
+
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)
|
|
1030
1074
|
# Add the number of contractions in the second symbol to the total count.
|
|
1031
1075
|
contract_count += len(_unique_summation_placeholders(second_symbol))
|
|
1032
1076
|
# Join the symbols of the two tensors to create a new symbol for the output tensor, with consecutive summation index placeholders.
|
|
@@ -1157,7 +1201,7 @@ class Tensor:
|
|
|
1157
1201
|
The names of any notebook variables that refer to this tensor object.
|
|
1158
1202
|
"""
|
|
1159
1203
|
# We get rid of the backticks since we just want a pure string, not a Markdown formatted string.
|
|
1160
|
-
return
|
|
1204
|
+
return _lookup_names_string(self).replace("`", "")
|
|
1161
1205
|
|
|
1162
1206
|
def __sub__(
|
|
1163
1207
|
self: Self,
|
|
@@ -1206,7 +1250,7 @@ class Tensor:
|
|
|
1206
1250
|
# Make sure the input is valid.
|
|
1207
1251
|
value = _validate_coordinates(value)
|
|
1208
1252
|
# Calculate the representation of this tensor in the new default coordinates, if it does not already exist. We do this for the default indices only.
|
|
1209
|
-
_ = self._calc_representation(
|
|
1253
|
+
_ = self._calc_representation(indices=self._default_indices, coords=value)
|
|
1210
1254
|
# Store the new default coordinates.
|
|
1211
1255
|
self._default_coords = value
|
|
1212
1256
|
|
|
@@ -1234,7 +1278,7 @@ class Tensor:
|
|
|
1234
1278
|
if len(value) != len(self._default_indices):
|
|
1235
1279
|
_handle_error("The number of indices must match the rank of the tensor.")
|
|
1236
1280
|
# Calculate the representation of this tensor in the new default indices, if it does not already exist. We do this for the default coordinates only.
|
|
1237
|
-
_ = self._calc_representation(self._default_coords
|
|
1281
|
+
_ = self._calc_representation(indices=value, coords=self._default_coords)
|
|
1238
1282
|
# Store the new default indices.
|
|
1239
1283
|
self._default_indices = value
|
|
1240
1284
|
|
|
@@ -1279,17 +1323,20 @@ class Tensor:
|
|
|
1279
1323
|
# Validate the coordinates and indices.
|
|
1280
1324
|
use_coords: Coordinates = self._validate_or_default_coords(coords)
|
|
1281
1325
|
use_indices: IndexConfiguration = self._validate_or_default_indices(indices)
|
|
1282
|
-
# Retrieve the components, calculating them if they have not already been calculated.
|
|
1283
|
-
components: s.Array = self._calc_representation(use_coords, use_indices)
|
|
1284
1326
|
# Optionally, show a warning if using the defaults, to ensure that the user knows which representation the components correspond to.
|
|
1285
1327
|
if warn:
|
|
1286
1328
|
warning: str = ""
|
|
1287
1329
|
if coords is None:
|
|
1288
|
-
warning = f"Using default coordinate system {
|
|
1330
|
+
warning = f"Using default coordinate system {_lookup_names_string(use_coords)}"
|
|
1289
1331
|
if indices is None:
|
|
1290
1332
|
warning += f"{"Using" if warning == "" else " and"} default index configuration {use_indices}"
|
|
1291
1333
|
if warning != "":
|
|
1292
1334
|
_display_markdown(f"**OGRePy**: {warning}.")
|
|
1335
|
+
# Retrieve the components, calculating them if they have not already been calculated.
|
|
1336
|
+
components: s.Array = self._calc_representation(indices=use_indices, coords=use_coords)
|
|
1337
|
+
# If this is a type of tensor that uses a curve parameter, replace any instance of the curve parameter placeholder with the selected curve parameter.
|
|
1338
|
+
if isinstance(self, CleanupCurveParameter):
|
|
1339
|
+
components = _subs_array(components, {_curve_parameter_placeholder: options.curve_parameter})
|
|
1293
1340
|
return components
|
|
1294
1341
|
|
|
1295
1342
|
def dim(
|
|
@@ -1320,15 +1367,15 @@ class Tensor:
|
|
|
1320
1367
|
"""
|
|
1321
1368
|
Display information about this tensor, including its class, symbol, rank, dimensions, default coordinates, default indices, and associated metric (if the tensor is not itself a metric), in human-readable form.
|
|
1322
1369
|
"""
|
|
1323
|
-
text: str = f"* **Name**: {
|
|
1370
|
+
text: str = f"* **Name**: {_lookup_names_string(self)}\n"
|
|
1324
1371
|
text += f"* **Class**: `{type(self).__name__}`\n"
|
|
1325
1372
|
text += f"* **Symbol**: ${self._tex_symbol(indices=self._default_indices, letters=options.index_letters)}$\n"
|
|
1326
1373
|
text += f"* **Rank**: {self.rank()}\n"
|
|
1327
1374
|
text += f"* **Dimensions**: {self.dim()}\n"
|
|
1328
|
-
text += f"* **Default Coordinates**: {
|
|
1375
|
+
text += f"* **Default Coordinates**: {_lookup_names_string(self._default_coords)}\n"
|
|
1329
1376
|
text += f"* **Default Indices**: {self._default_indices}\n"
|
|
1330
1377
|
if not isinstance(self, Metric):
|
|
1331
|
-
text += f"* **Metric**: {
|
|
1378
|
+
text += f"* **Metric**: {_lookup_names_string(self._metric)}\n"
|
|
1332
1379
|
else:
|
|
1333
1380
|
used_by: list[str] = _using_metric(self)
|
|
1334
1381
|
text += f"* **Associated Metric For**: {", ".join(used_by) if len(used_by) > 0 else "None"}\n"
|
|
@@ -1530,7 +1577,7 @@ class Tensor:
|
|
|
1530
1577
|
# If the tensor is a scalar, then populating the dictionary is very simple: keep it empty if the scalar is zero, otherwise populate it with a single key pointing to a list with a single item.
|
|
1531
1578
|
if rank == 0:
|
|
1532
1579
|
if components[0] != 0:
|
|
1533
|
-
non_zero[components[0]] = [self.
|
|
1580
|
+
non_zero[components[0]] = [self._tex_symbol(indices=use_indices, letters=[])]
|
|
1534
1581
|
# If the tensor is not a scalar, then we need to go over all the components one by one.
|
|
1535
1582
|
else:
|
|
1536
1583
|
# Instead of the default index letters, we will use the coordinate letters themselves (e.g.: t, x, y, z) to display the tensor components.
|
|
@@ -1615,14 +1662,15 @@ class Tensor:
|
|
|
1615
1662
|
|
|
1616
1663
|
def _calc_representation(
|
|
1617
1664
|
self: Self,
|
|
1618
|
-
|
|
1665
|
+
*,
|
|
1619
1666
|
indices: IndexConfiguration,
|
|
1667
|
+
coords: Coordinates,
|
|
1620
1668
|
) -> s.Array:
|
|
1621
1669
|
"""
|
|
1622
1670
|
Get the components of this tensor in the given representation, calculating them if they have not been calculated yet.
|
|
1623
1671
|
#### Parameters:
|
|
1624
|
-
* `coords`: An OGRePy `Coordinates` object specifying the coordinate system of the representation.
|
|
1625
1672
|
* `indices`: A tuple of integers specifying the index configuration of the representation. Each integer in the tuple can be either +1 for an upper index or -1 for a lower index.
|
|
1673
|
+
* `coords`: An OGRePy `Coordinates` object specifying the coordinate system of the representation.
|
|
1626
1674
|
#### Returns:
|
|
1627
1675
|
The desired components.
|
|
1628
1676
|
"""
|
|
@@ -1672,7 +1720,7 @@ class Tensor:
|
|
|
1672
1720
|
"""
|
|
1673
1721
|
# Obtain the coordinate symbols.
|
|
1674
1722
|
coord_symbols: list[s.Symbol] = coords.components().tolist()
|
|
1675
|
-
# If this object has the mixin class `CleanupTimeParameter`, the parameter with respect to which we need to clean up is the time parameter (assumed to be the first coordinate). Otherwise, it is the curve parameter if the object has `CleanupCurveParameter`, or simply irrelevant if it has neither class.
|
|
1723
|
+
# If this object has the mixin class `CleanupTimeParameter`, the parameter with respect to which we need to clean up is the time parameter (assumed to be the first coordinate). Otherwise, it is the curve parameter if the object has `CleanupCurveParameter`, or simply irrelevant if it has neither class. Note that when this function is called, the curve parameter placeholder has already been replaced with the real curve parameter.
|
|
1676
1724
|
param: s.Symbol = coord_symbols[0] if isinstance(self, CleanupTimeParameter) else options.curve_parameter
|
|
1677
1725
|
if isinstance(self, CleanupCurveParameter | CleanupTimeParameter):
|
|
1678
1726
|
# If this is a type of tensor that uses a parameter, replace any instance of the coordinate functions in terms of the parameter with the ordinary coordinate symbols, and similarly for their first and second derivatives.
|
|
@@ -1725,7 +1773,10 @@ class Tensor:
|
|
|
1725
1773
|
use_coords: Coordinates = self._validate_or_default_coords(coords)
|
|
1726
1774
|
use_indices: IndexConfiguration = self._validate_or_default_indices(indices)
|
|
1727
1775
|
# Retrieve the components, calculating them if they have not already been calculated.
|
|
1728
|
-
components: s.Array = self._calc_representation(
|
|
1776
|
+
components: s.Array = self._calc_representation(indices=use_indices, coords=use_coords)
|
|
1777
|
+
# If this is a type of tensor that uses a curve parameter, replace any instance of the curve parameter placeholder with the selected curve parameter.
|
|
1778
|
+
if isinstance(self, CleanupCurveParameter):
|
|
1779
|
+
components = _subs_array(components, {_curve_parameter_placeholder: options.curve_parameter})
|
|
1729
1780
|
# Apply the function, if any.
|
|
1730
1781
|
if function is not None:
|
|
1731
1782
|
components = _map_function(components, function)
|
|
@@ -1735,7 +1786,7 @@ class Tensor:
|
|
|
1735
1786
|
# Simplify the components, if desired.
|
|
1736
1787
|
if simplify is True:
|
|
1737
1788
|
components = _simplify_array(components)
|
|
1738
|
-
# Clean up the notation
|
|
1789
|
+
# Clean up the notation.
|
|
1739
1790
|
components = self._cleanup_notation(components=components, coords=use_coords)
|
|
1740
1791
|
# Return the results.
|
|
1741
1792
|
return (use_coords, use_indices, components)
|
|
@@ -1816,7 +1867,7 @@ class Tensor:
|
|
|
1816
1867
|
# Remove all the indices we marked for removal earlier, and save the index configuration for the output tensor as a tuple.
|
|
1817
1868
|
out_indices: IndexConfiguration = tuple(i for i in out_indices_list if i != 0)
|
|
1818
1869
|
# Retrieve the components in the desired representation, calculating it if it does not already exist.
|
|
1819
|
-
components: s.Array = self._calc_representation(
|
|
1870
|
+
components: s.Array = self._calc_representation(indices=tuple(in_indices), coords=self._default_coords)
|
|
1820
1871
|
# Calculate the trace by contracting on the repeated indices.
|
|
1821
1872
|
result: TensorWithIndexSpecification = _contract((components, [*all_letters]))
|
|
1822
1873
|
# Simplify and store the result in a new tensor object.
|
|
@@ -1897,9 +1948,9 @@ class Tensor:
|
|
|
1897
1948
|
"""
|
|
1898
1949
|
# Get the source components.
|
|
1899
1950
|
components: s.Array = self._get_components(indices=source_indices, coords=coords)
|
|
1900
|
-
# Get the components of the metric and inverse metric in the relevant coordinate system, calculating them on the spot if the metric has not been used in this coordinate system before. (Note that there is no risk of an infinite loop here
|
|
1901
|
-
metric: s.Array = self._metric.
|
|
1902
|
-
inverse_metric: s.Array = self._metric.
|
|
1951
|
+
# Get the components of the metric and inverse metric in the relevant coordinate system, calculating them on the spot if the metric has not been used in this coordinate system before. (Note that there is no risk of an infinite loop here, since the `Metric` subclass pre-calculates the representations in all index configurations when it transforms coordinates, and therefore does not use this method.)
|
|
1952
|
+
metric: s.Array = self._metric._calc_representation(indices=(-1, -1), coords=coords)
|
|
1953
|
+
inverse_metric: s.Array = self._metric._calc_representation(indices=(1, 1), coords=coords)
|
|
1903
1954
|
# `in_vars` will contain the variables to be used in the input tensor.
|
|
1904
1955
|
in_vars: IndexSpecification = []
|
|
1905
1956
|
# `permute_vars` will contain the variables that we want to permute the final result to (see below for explanation).
|
|
@@ -2031,9 +2082,9 @@ class Christoffel(Tensor, FixedDefaultIndices):
|
|
|
2031
2082
|
# Sum three such partial derivatives.
|
|
2032
2083
|
sum_partial_metric: Tensor = partial_metric("mu nu sigma") + partial_metric("nu sigma mu") - partial_metric("sigma mu nu")
|
|
2033
2084
|
# Extract the inverse metric and multiply by half.
|
|
2034
|
-
half_inverse_metric: s.Array = cast(s.Array, s.Rational(1, 2) * metric.
|
|
2085
|
+
half_inverse_metric: s.Array = cast(s.Array, s.Rational(1, 2) * metric._calc_representation(indices=(+1, +1), coords=coords))
|
|
2035
2086
|
# Calculate the Christoffel symbols by contracting the sum of partial derivatives of the metric with half the inverse metric.
|
|
2036
|
-
result: s.Array = _contract((half_inverse_metric, ["lamda", "sigma"]), (sum_partial_metric.
|
|
2087
|
+
result: s.Array = _contract((half_inverse_metric, ["lamda", "sigma"]), (sum_partial_metric._calc_representation(indices=(-1, -1, -1), coords=coords), ["mu", "nu", "sigma"]))[0]
|
|
2037
2088
|
# Simplify and return the result.
|
|
2038
2089
|
return _simplify_array(result)
|
|
2039
2090
|
|
|
@@ -2108,7 +2159,7 @@ class Einstein(Tensor, FixedDefaultIndices):
|
|
|
2108
2159
|
#### Returns:
|
|
2109
2160
|
The calculated components.
|
|
2110
2161
|
"""
|
|
2111
|
-
return (metric.ricci_tensor("mu nu") - s.Rational(1, 2) * metric.ricci_scalar() @ metric("mu nu")).
|
|
2162
|
+
return (metric.ricci_tensor("mu nu") - s.Rational(1, 2) * metric.ricci_scalar() @ metric("mu nu"))._calc_representation(indices=(-1, -1), coords=coords)
|
|
2112
2163
|
|
|
2113
2164
|
|
|
2114
2165
|
class GeodesicFromChristoffel(Tensor, CleanupCurveParameter, FixedDefaultIndices):
|
|
@@ -2161,11 +2212,11 @@ class GeodesicFromChristoffel(Tensor, CleanupCurveParameter, FixedDefaultIndices
|
|
|
2161
2212
|
The calculated components.
|
|
2162
2213
|
"""
|
|
2163
2214
|
# Create a tangent vector whose components are the first derivatives of the coordinate symbols as functions of the curve parameter.
|
|
2164
|
-
tangent: s.Array = s.Array(coords.of_param_dot())
|
|
2215
|
+
tangent: s.Array = s.Array(coords.of_param_dot(_curve_parameter_placeholder))
|
|
2165
2216
|
# Create an acceleration vector whose components are the second derivatives of the coordinate symbols as functions of the curve parameter.
|
|
2166
|
-
accel: s.Array = s.Array(coords.of_param_ddot())
|
|
2217
|
+
accel: s.Array = s.Array(coords.of_param_ddot(_curve_parameter_placeholder))
|
|
2167
2218
|
# Obtain the Christoffel symbols, and replace any instance of the coordinate symbols with coordinate functions of the curve parameter.
|
|
2168
|
-
christoffel_with_param: s.Array = _subs_array(metric.christoffel().
|
|
2219
|
+
christoffel_with_param: s.Array = _subs_array(metric.christoffel()._calc_representation(indices=(+1, -1, -1), coords=coords), coords.of_param_dict(_curve_parameter_placeholder))
|
|
2169
2220
|
# Calculate the geodesic equations by contracting the Christoffel symbols with two tangent vectors and adding to the acceleration vector.
|
|
2170
2221
|
result: s.Array = accel + _contract((christoffel_with_param, ["sigma", "mu", "nu"]), (tangent, ["mu"]), (tangent, ["nu"]))[0]
|
|
2171
2222
|
# Simplify and return the result.
|
|
@@ -2240,13 +2291,13 @@ class GeodesicFromLagrangian(Tensor, CleanupCurveParameter, FixedDefaultIndices)
|
|
|
2240
2291
|
The calculated components.
|
|
2241
2292
|
"""
|
|
2242
2293
|
# Make sure we have the components of the Lagrangian in the correct coordinate system. We divide them by 2 since geodesics calculated in this way will inevitably get additional factors of 2 from taking the derivatives of squares.
|
|
2243
|
-
components: s.Expr = cast(s.Expr, s.Rational(1, 2) * metric.lagrangian()._calc_representation(
|
|
2294
|
+
components: s.Expr = cast(s.Expr, s.Rational(1, 2) * metric.lagrangian()._calc_representation(indices=(), coords=coords)[0])
|
|
2244
2295
|
# Calculate the dL/dx part of the Euler-Lagrange equation.
|
|
2245
|
-
euler_lagrange_x: s.Array = s.Array([components.diff(x) for x in coords.of_param()])
|
|
2296
|
+
euler_lagrange_x: s.Array = s.Array([components.diff(x) for x in coords.of_param(_curve_parameter_placeholder)])
|
|
2246
2297
|
# Calculate the dL/dx_dot part of the Euler-Lagrange equation.
|
|
2247
|
-
euler_lagrange_x_dot: s.Array = s.Array([components.diff(x_dot) for x_dot in coords.of_param_dot()])
|
|
2248
|
-
# Take the derivative of the dL/dx_dot part with respect to the curve parameter, but leave the derivative unevaluated.
|
|
2249
|
-
euler_lagrange_x_dot_diff: s.Array = s.Array([s.Derivative(term,
|
|
2298
|
+
euler_lagrange_x_dot: s.Array = s.Array([components.diff(x_dot) for x_dot in coords.of_param_dot(_curve_parameter_placeholder)])
|
|
2299
|
+
# Take the derivative of the dL/dx_dot part with respect to the curve parameter placeholder, but leave the derivative unevaluated.
|
|
2300
|
+
euler_lagrange_x_dot_diff: s.Array = s.Array([s.Derivative(term, _curve_parameter_placeholder) for term in euler_lagrange_x_dot])
|
|
2250
2301
|
# Calculate the geodesic equation vector from the full Euler-Lagrange equation.
|
|
2251
2302
|
result: s.Array = euler_lagrange_x - euler_lagrange_x_dot_diff
|
|
2252
2303
|
# Simplify and return the result, making sure to pass `doit=False` so the derivatives with respect to the curve parameter will not be evaluated.
|
|
@@ -2329,7 +2380,7 @@ class GeodesicTimeParam(Tensor, CleanupTimeParameter, FixedDefaultIndices):
|
|
|
2329
2380
|
# Obtain the Christoffel symbols, and replace any instance of the spatial coordinate symbols with coordinate functions of time.
|
|
2330
2381
|
param_dict: dict[s.Symbol, AppliedUndef] = coords.of_param_dict(time)
|
|
2331
2382
|
del param_dict[time]
|
|
2332
|
-
christoffel_with_param: s.Array = _subs_array(metric.christoffel().
|
|
2383
|
+
christoffel_with_param: s.Array = _subs_array(metric.christoffel()._calc_representation(indices=(+1, -1, -1), coords=coords), param_dict)
|
|
2333
2384
|
# We also need the Christoffel symbols with 0 in the first index, which is a rank-2 tensor.
|
|
2334
2385
|
christoffel_zero: s.Array = cast(s.Array, christoffel_with_param[0, :, :])
|
|
2335
2386
|
# Contract the Christoffel symbols with the tangent vector and subtract from the Christoffel symbols with 0 in the first index to get the term in the parentheses in the equation (see `Metric.geodesic_time_param()` docs), which is a rank-3 tensor.
|
|
@@ -2407,7 +2458,7 @@ class Kretschmann(Tensor, FixedDefaultIndices):
|
|
|
2407
2458
|
#### Returns:
|
|
2408
2459
|
The calculated components.
|
|
2409
2460
|
"""
|
|
2410
|
-
return (metric.riemann("rho sigma mu nu") @ metric.riemann("rho sigma mu nu")).
|
|
2461
|
+
return (metric.riemann("rho sigma mu nu") @ metric.riemann("rho sigma mu nu"))._calc_representation(indices=(), coords=coords)
|
|
2411
2462
|
|
|
2412
2463
|
|
|
2413
2464
|
class Lagrangian(Tensor, CleanupCurveParameter, FixedDefaultIndices):
|
|
@@ -2460,9 +2511,9 @@ class Lagrangian(Tensor, CleanupCurveParameter, FixedDefaultIndices):
|
|
|
2460
2511
|
The calculated components.
|
|
2461
2512
|
"""
|
|
2462
2513
|
# Create a tangent vector whose components are the first derivatives of the coordinate symbols as functions of the curve parameter.
|
|
2463
|
-
tangent: s.Array = s.Array(coords.of_param_dot())
|
|
2514
|
+
tangent: s.Array = s.Array(coords.of_param_dot(_curve_parameter_placeholder))
|
|
2464
2515
|
# Obtain the metric components, and replace any instance of the coordinate symbols with coordinate functions of the curve parameter.
|
|
2465
|
-
metric_with_param: s.Array = _subs_array(metric.
|
|
2516
|
+
metric_with_param: s.Array = _subs_array(metric._calc_representation(indices=(-1, -1), coords=coords), coords.of_param_dict(_curve_parameter_placeholder))
|
|
2466
2517
|
# Calculate the Lagrangian by taking the norm squared of the tangent vector.
|
|
2467
2518
|
result: s.Array = _contract((metric_with_param, ["mu", "nu"]), (tangent, ["mu"]), (tangent, ["nu"]))[0]
|
|
2468
2519
|
# Simplify and return the result.
|
|
@@ -2680,7 +2731,7 @@ class Metric(Tensor, FixedDefaultIndices):
|
|
|
2680
2731
|
# Validate the input.
|
|
2681
2732
|
use_coords: Coordinates = self._validate_or_default_coords(coords)
|
|
2682
2733
|
# Calculate the components of the metric in the desired coordinate system, in case they have not already been calculated.
|
|
2683
|
-
components: s.Array = self._calc_representation(
|
|
2734
|
+
components: s.Array = self._calc_representation(indices=(-1, -1), coords=use_coords)
|
|
2684
2735
|
# Convert each of the coordinate symbols into the corresponding differential.
|
|
2685
2736
|
diff: s.Array = s.Array([sym(r"\mathrm{d}" + _to_tex(symbol)) for symbol in use_coords.components()])
|
|
2686
2737
|
# Sum the matrix elements with the corresponding differentials and return the result.
|
|
@@ -2747,7 +2798,7 @@ class Metric(Tensor, FixedDefaultIndices):
|
|
|
2747
2798
|
# Validate the input.
|
|
2748
2799
|
use_coords: Coordinates = self._validate_or_default_coords(coords)
|
|
2749
2800
|
# Calculate the components of the metric in the desired coordinate system, in case they have not already been calculated.
|
|
2750
|
-
components: s.Array = self._calc_representation(
|
|
2801
|
+
components: s.Array = self._calc_representation(indices=(-1, -1), coords=use_coords)
|
|
2751
2802
|
# Calculate the determinant, simplify it, and return the result.
|
|
2752
2803
|
return _simplify(cast(s.Basic, s.Matrix(components).det()))
|
|
2753
2804
|
|
|
@@ -2827,7 +2878,7 @@ class RicciScalar(Tensor, FixedDefaultIndices):
|
|
|
2827
2878
|
#### Returns:
|
|
2828
2879
|
The calculated components.
|
|
2829
2880
|
"""
|
|
2830
|
-
return
|
|
2881
|
+
return metric.ricci_tensor("mu mu")._calc_representation(indices=(), coords=coords)
|
|
2831
2882
|
|
|
2832
2883
|
|
|
2833
2884
|
class RicciTensor(Tensor, FixedDefaultIndices):
|
|
@@ -2879,7 +2930,7 @@ class RicciTensor(Tensor, FixedDefaultIndices):
|
|
|
2879
2930
|
#### Returns:
|
|
2880
2931
|
The calculated components.
|
|
2881
2932
|
"""
|
|
2882
|
-
return
|
|
2933
|
+
return metric.riemann("lamda mu lamda nu")._calc_representation(indices=(-1, -1), coords=coords)
|
|
2883
2934
|
|
|
2884
2935
|
|
|
2885
2936
|
class Riemann(Tensor, FixedDefaultIndices):
|
|
@@ -2933,7 +2984,7 @@ class Riemann(Tensor, FixedDefaultIndices):
|
|
|
2933
2984
|
"""
|
|
2934
2985
|
riemann: Tensor = PartialD("mu") @ metric.christoffel("rho nu sigma") - PartialD("nu") @ metric.christoffel("rho mu sigma") + metric.christoffel("rho mu lamda") @ metric.christoffel("lamda nu sigma") - metric.christoffel("rho nu lamda") @ metric.christoffel("lamda mu sigma")
|
|
2935
2986
|
riemann.permute("rho sigma mu nu")
|
|
2936
|
-
return riemann.
|
|
2987
|
+
return riemann._calc_representation(indices=(+1, -1, -1, -1), coords=coords)
|
|
2937
2988
|
|
|
2938
2989
|
|
|
2939
2990
|
#################################################################################################
|
|
@@ -3234,15 +3285,28 @@ def _list_references(
|
|
|
3234
3285
|
|
|
3235
3286
|
def _lookup_names(
|
|
3236
3287
|
ref: object,
|
|
3288
|
+
) -> list[str]:
|
|
3289
|
+
"""
|
|
3290
|
+
Look up the names of the notebook variables corresponding to a specific object reference and return them as a list of strings.
|
|
3291
|
+
#### Parameters:
|
|
3292
|
+
* `ref`: The reference to look up.
|
|
3293
|
+
#### Returns:
|
|
3294
|
+
A list of strings containing the names of all of the variables that point to the given reference. Note that any variables that start with an underscore are ignored, to avoid e.g. the `_` variable, which is the result of the last evaluation, as well as other similar internal variables.
|
|
3295
|
+
"""
|
|
3296
|
+
return [name for name, value in __main__.__dict__.items() if value is ref and not name.startswith("_")]
|
|
3297
|
+
|
|
3298
|
+
|
|
3299
|
+
def _lookup_names_string(
|
|
3300
|
+
ref: object,
|
|
3237
3301
|
) -> str:
|
|
3238
3302
|
"""
|
|
3239
|
-
Look up the names of the notebook variables corresponding to a specific object reference.
|
|
3303
|
+
Look up the names of the notebook variables corresponding to a specific object reference and return them as a string.
|
|
3240
3304
|
#### Parameters:
|
|
3241
3305
|
* `ref`: The reference to look up.
|
|
3242
3306
|
#### Returns:
|
|
3243
3307
|
A string containing the name of the first variable that points to the given reference, along with any aliases, or "[no name]" if there is no match. Note that any variables that start with an underscore are ignored, to avoid e.g. the `_` variable, which is the result of the last evaluation, as well as other similar internal variables.
|
|
3244
3308
|
"""
|
|
3245
|
-
return _list_aliases(
|
|
3309
|
+
return _list_aliases(_lookup_names(ref))
|
|
3246
3310
|
|
|
3247
3311
|
|
|
3248
3312
|
def _make_replacement(
|
|
@@ -3358,10 +3422,10 @@ def _subs_array(
|
|
|
3358
3422
|
Perform a substitution in a SymPy `Array`.
|
|
3359
3423
|
#### Parameters:
|
|
3360
3424
|
* `array`: The array to perform the substitution in.
|
|
3361
|
-
* `args` (optional): Zero or more positional arguments to pass to the function.
|
|
3362
|
-
* `kwargs` (optional): Zero or more keyword arguments to pass to the function.
|
|
3425
|
+
* `args` (optional): Zero or more positional arguments to pass to the `subs()` function.
|
|
3426
|
+
* `kwargs` (optional): Zero or more keyword arguments to pass to the `subs()` function.
|
|
3363
3427
|
#### Returns:
|
|
3364
|
-
The
|
|
3428
|
+
The array with the substitution performed.
|
|
3365
3429
|
"""
|
|
3366
3430
|
return cast(s.Array, array.subs(*args, **kwargs))
|
|
3367
3431
|
|
|
@@ -3827,3 +3891,31 @@ type TensorWithIndexSpecification = tuple[s.Array, IndexSpecification]
|
|
|
3827
3891
|
|
|
3828
3892
|
# The tensor's components are stored as values in a dictionary, where the keys are tuples of the form (indices, coords).
|
|
3829
3893
|
type Components = dict[tuple[IndexConfiguration, Coordinates], s.Array]
|
|
3894
|
+
|
|
3895
|
+
|
|
3896
|
+
#####################
|
|
3897
|
+
# Private variables #
|
|
3898
|
+
#####################
|
|
3899
|
+
|
|
3900
|
+
|
|
3901
|
+
# Create a thread pool executor for later use.
|
|
3902
|
+
_pool = concurrent.futures.ThreadPoolExecutor()
|
|
3903
|
+
|
|
3904
|
+
# The symbol to use as curve parameter placeholder.
|
|
3905
|
+
_curve_parameter_placeholder: s.Symbol = sym("[curve]")
|
|
3906
|
+
|
|
3907
|
+
|
|
3908
|
+
########################
|
|
3909
|
+
# Initialization tasks #
|
|
3910
|
+
########################
|
|
3911
|
+
|
|
3912
|
+
|
|
3913
|
+
# Display the welcome message, but not if `OGREPY_DISABLE_WELCOME = True` was defined in the notebook or the environment variable `OGREPY_DISABLE_WELCOME` was set to "True" before importing the package.
|
|
3914
|
+
disable_welcome: bool = __main__.__dict__.get("OGREPY_DISABLE_WELCOME", False) is True or os.environ.get("OGREPY_DISABLE_WELCOME", "False") == "True"
|
|
3915
|
+
if not disable_welcome:
|
|
3916
|
+
welcome()
|
|
3917
|
+
|
|
3918
|
+
# Check for package updates, but not if `OGREPY_DISABLE_UPDATE_CHECK = True` was defined in the notebook or the environment variable `OGREPY_DISABLE_UPDATE_CHECK` was set to "True" before importing the package.If the welcome message is disabled, this is done quietly (only notifies if a new version of the package is available).
|
|
3919
|
+
disable_update_check: bool = __main__.__dict__.get("OGREPY_DISABLE_UPDATE_CHECK", False) is True or os.environ.get("OGREPY_DISABLE_UPDATE_CHECK", "False") == "True"
|
|
3920
|
+
if not disable_update_check:
|
|
3921
|
+
_ = _pool.submit(update_check, quiet=disable_welcome)
|