gwsim 0.1.0__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.
- gwsim/__init__.py +11 -0
- gwsim/__main__.py +8 -0
- gwsim/cli/__init__.py +0 -0
- gwsim/cli/config.py +88 -0
- gwsim/cli/default_config.py +56 -0
- gwsim/cli/main.py +101 -0
- gwsim/cli/merge.py +150 -0
- gwsim/cli/repository/__init__.py +0 -0
- gwsim/cli/repository/create.py +91 -0
- gwsim/cli/repository/delete.py +51 -0
- gwsim/cli/repository/download.py +54 -0
- gwsim/cli/repository/list_depositions.py +63 -0
- gwsim/cli/repository/main.py +38 -0
- gwsim/cli/repository/metadata/__init__.py +0 -0
- gwsim/cli/repository/metadata/main.py +24 -0
- gwsim/cli/repository/metadata/update.py +58 -0
- gwsim/cli/repository/publish.py +52 -0
- gwsim/cli/repository/upload.py +74 -0
- gwsim/cli/repository/utils.py +47 -0
- gwsim/cli/repository/verify.py +61 -0
- gwsim/cli/simulate.py +220 -0
- gwsim/cli/simulate_utils.py +596 -0
- gwsim/cli/utils/__init__.py +85 -0
- gwsim/cli/utils/checkpoint.py +178 -0
- gwsim/cli/utils/config.py +347 -0
- gwsim/cli/utils/hash.py +23 -0
- gwsim/cli/utils/retry.py +62 -0
- gwsim/cli/utils/simulation_plan.py +439 -0
- gwsim/cli/utils/template.py +56 -0
- gwsim/cli/utils/utils.py +149 -0
- gwsim/cli/validate.py +255 -0
- gwsim/data/__init__.py +8 -0
- gwsim/data/serialize/__init__.py +9 -0
- gwsim/data/serialize/decoder.py +59 -0
- gwsim/data/serialize/encoder.py +44 -0
- gwsim/data/serialize/serializable.py +33 -0
- gwsim/data/time_series/__init__.py +3 -0
- gwsim/data/time_series/inject.py +104 -0
- gwsim/data/time_series/time_series.py +355 -0
- gwsim/data/time_series/time_series_list.py +182 -0
- gwsim/detector/__init__.py +8 -0
- gwsim/detector/base.py +156 -0
- gwsim/detector/detectors/E1_2L_Aligned_Sardinia.interferometer +22 -0
- gwsim/detector/detectors/E1_2L_Misaligned_Sardinia.interferometer +22 -0
- gwsim/detector/detectors/E1_Triangle_EMR.interferometer +19 -0
- gwsim/detector/detectors/E1_Triangle_Sardinia.interferometer +19 -0
- gwsim/detector/detectors/E2_2L_Aligned_EMR.interferometer +22 -0
- gwsim/detector/detectors/E2_2L_Misaligned_EMR.interferometer +22 -0
- gwsim/detector/detectors/E2_Triangle_EMR.interferometer +19 -0
- gwsim/detector/detectors/E2_Triangle_Sardinia.interferometer +19 -0
- gwsim/detector/detectors/E3_Triangle_EMR.interferometer +19 -0
- gwsim/detector/detectors/E3_Triangle_Sardinia.interferometer +19 -0
- gwsim/detector/noise_curves/ET_10_HF_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_10_full_cryo_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_15_HF_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_15_full_cryo_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_20_HF_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_20_full_cryo_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_D_psd.txt +3000 -0
- gwsim/detector/utils.py +90 -0
- gwsim/glitch/__init__.py +7 -0
- gwsim/glitch/base.py +69 -0
- gwsim/mixin/__init__.py +8 -0
- gwsim/mixin/detector.py +203 -0
- gwsim/mixin/gwf.py +192 -0
- gwsim/mixin/population_reader.py +175 -0
- gwsim/mixin/randomness.py +107 -0
- gwsim/mixin/time_series.py +295 -0
- gwsim/mixin/waveform.py +47 -0
- gwsim/noise/__init__.py +19 -0
- gwsim/noise/base.py +134 -0
- gwsim/noise/bilby_stationary_gaussian.py +117 -0
- gwsim/noise/colored_noise.py +275 -0
- gwsim/noise/correlated_noise.py +257 -0
- gwsim/noise/pycbc_stationary_gaussian.py +112 -0
- gwsim/noise/stationary_gaussian.py +44 -0
- gwsim/noise/white_noise.py +51 -0
- gwsim/repository/__init__.py +0 -0
- gwsim/repository/zenodo.py +269 -0
- gwsim/signal/__init__.py +11 -0
- gwsim/signal/base.py +137 -0
- gwsim/signal/cbc.py +61 -0
- gwsim/simulator/__init__.py +7 -0
- gwsim/simulator/base.py +315 -0
- gwsim/simulator/state.py +85 -0
- gwsim/utils/__init__.py +11 -0
- gwsim/utils/datetime_parser.py +44 -0
- gwsim/utils/et_2l_geometry.py +165 -0
- gwsim/utils/io.py +167 -0
- gwsim/utils/log.py +145 -0
- gwsim/utils/population.py +48 -0
- gwsim/utils/random.py +69 -0
- gwsim/utils/retry.py +75 -0
- gwsim/utils/triangular_et_geometry.py +164 -0
- gwsim/version.py +7 -0
- gwsim/waveform/__init__.py +7 -0
- gwsim/waveform/factory.py +83 -0
- gwsim/waveform/pycbc_wrapper.py +37 -0
- gwsim-0.1.0.dist-info/METADATA +157 -0
- gwsim-0.1.0.dist-info/RECORD +103 -0
- gwsim-0.1.0.dist-info/WHEEL +4 -0
- gwsim-0.1.0.dist-info/entry_points.txt +2 -0
- gwsim-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""CLI for managing Zenodo repositories."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
# Create the repository subcommand app
|
|
8
|
+
repository_app = typer.Typer(
|
|
9
|
+
name="repository",
|
|
10
|
+
help="Manage Zenodo repositories for simulation data.",
|
|
11
|
+
rich_markup_mode="rich",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Import and register commands after app is created
|
|
16
|
+
def register_commands() -> None:
|
|
17
|
+
"""Register all CLI commands."""
|
|
18
|
+
|
|
19
|
+
from gwsim.cli.repository.create import create_command # pylint: disable=import-outside-toplevel
|
|
20
|
+
from gwsim.cli.repository.delete import delete_command # pylint: disable=import-outside-toplevel
|
|
21
|
+
from gwsim.cli.repository.download import download_command # pylint: disable=import-outside-toplevel
|
|
22
|
+
from gwsim.cli.repository.list_depositions import ( # pylint: disable=import-outside-toplevel
|
|
23
|
+
list_depositions_command,
|
|
24
|
+
)
|
|
25
|
+
from gwsim.cli.repository.metadata.main import metadata_app # pylint: disable=import-outside-toplevel
|
|
26
|
+
from gwsim.cli.repository.upload import upload_command # pylint: disable=import-outside-toplevel
|
|
27
|
+
from gwsim.cli.repository.verify import verify_command # pylint: disable=import-outside-toplevel
|
|
28
|
+
|
|
29
|
+
repository_app.command("create")(create_command)
|
|
30
|
+
repository_app.command("upload")(upload_command)
|
|
31
|
+
repository_app.command("download")(download_command)
|
|
32
|
+
repository_app.command("list")(list_depositions_command)
|
|
33
|
+
repository_app.command("delete")(delete_command)
|
|
34
|
+
repository_app.command("verify")(verify_command)
|
|
35
|
+
repository_app.add_typer(metadata_app, name="metadata", help="Manage Zenodo metadata")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
register_commands()
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""CLI for managing Zenodo metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
# Create the metadata subcommand app
|
|
8
|
+
metadata_app = typer.Typer(
|
|
9
|
+
name="metadata",
|
|
10
|
+
help="Manage Zenodo metadata for simulation data.",
|
|
11
|
+
rich_markup_mode="rich",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Import and register commands after app is created
|
|
16
|
+
def register_commands() -> None:
|
|
17
|
+
"""Register all CLI commands."""
|
|
18
|
+
|
|
19
|
+
from gwsim.cli.repository.metadata.update import update_command # pylint: disable=import-outside-toplevel
|
|
20
|
+
|
|
21
|
+
metadata_app.command("update")(update_command)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
register_commands()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""CLI for updating Zenodo repository depositions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def update_command(
|
|
12
|
+
deposition_id: Annotated[str, typer.Argument(help="Deposition ID")],
|
|
13
|
+
metadata_file: Annotated[
|
|
14
|
+
Path | None, typer.Option("--metadata-file", help="YAML file with metadata to update")
|
|
15
|
+
] = None,
|
|
16
|
+
sandbox: Annotated[bool, typer.Option("--sandbox", help="Use sandbox environment")] = False,
|
|
17
|
+
token: Annotated[str | None, typer.Option("--token", help="Zenodo access token")] = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Update metadata for a deposition.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
gwsim repository update 123456 --metadata-file metadata.yaml
|
|
23
|
+
"""
|
|
24
|
+
import logging # pylint: disable=import-outside-toplevel
|
|
25
|
+
|
|
26
|
+
import yaml # pylint: disable=import-outside-toplevel
|
|
27
|
+
from rich.console import Console # pylint: disable=import-outside-toplevel
|
|
28
|
+
|
|
29
|
+
from gwsim.cli.repository.utils import get_zenodo_client # pylint: disable=import-outside-toplevel
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("gwsim")
|
|
32
|
+
console = Console()
|
|
33
|
+
|
|
34
|
+
if not metadata_file:
|
|
35
|
+
metadata_file = Path(typer.prompt("Path to metadata YAML file"))
|
|
36
|
+
|
|
37
|
+
if not metadata_file.exists():
|
|
38
|
+
console.print(f"[red]Error:[/red] File not found: {metadata_file}")
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
|
|
41
|
+
with metadata_file.open("r", encoding="utf-8") as f:
|
|
42
|
+
metadata = yaml.safe_load(f)
|
|
43
|
+
|
|
44
|
+
if not metadata:
|
|
45
|
+
console.print("[red]Error:[/red] Metadata file is empty.")
|
|
46
|
+
raise typer.Exit(1)
|
|
47
|
+
|
|
48
|
+
client = get_zenodo_client(sandbox=sandbox, token=token)
|
|
49
|
+
|
|
50
|
+
console.print(f"[bold blue]Updating metadata for deposition {deposition_id}...[/bold blue]")
|
|
51
|
+
try:
|
|
52
|
+
client.update_metadata(deposition_id, metadata)
|
|
53
|
+
console.print("[green]✓ Metadata updated successfully[/green]")
|
|
54
|
+
console.print(f"[cyan]Next:[/cyan] gwsim repository publish {deposition_id}")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
console.print(f"[red]✗ Failed to update metadata: {e}[/red]")
|
|
57
|
+
logger.error("Update metadata failed: %s", e)
|
|
58
|
+
raise typer.Exit(1) from e
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""CLI for publishing Zenodo repository depositions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def publish( # pylint: disable=import-outside-toplevel
|
|
11
|
+
deposition_id: Annotated[str, typer.Argument(help="Deposition ID")],
|
|
12
|
+
sandbox: Annotated[bool, typer.Option("--sandbox", help="Use sandbox environment")] = False,
|
|
13
|
+
token: Annotated[str | None, typer.Option("--token", help="Zenodo access token")] = None,
|
|
14
|
+
) -> None:
|
|
15
|
+
"""Publish a deposition to Zenodo.
|
|
16
|
+
|
|
17
|
+
Warning: Publishing is permanent and cannot be undone.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
gwsim repository publish 123456
|
|
21
|
+
"""
|
|
22
|
+
import logging
|
|
23
|
+
|
|
24
|
+
from rich.console import Console
|
|
25
|
+
|
|
26
|
+
from gwsim.cli.repository.utils import get_zenodo_client
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger("gwsim")
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
31
|
+
if not typer.confirm(
|
|
32
|
+
f"[yellow]Publish deposition {deposition_id}?[/yellow] This action is permanent and cannot be undone."
|
|
33
|
+
):
|
|
34
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
35
|
+
raise typer.Exit(0)
|
|
36
|
+
|
|
37
|
+
client = get_zenodo_client(sandbox=sandbox, token=token)
|
|
38
|
+
|
|
39
|
+
console.print(f"[bold blue]Publishing deposition {deposition_id}...[/bold blue]")
|
|
40
|
+
try:
|
|
41
|
+
result = client.publish_deposition(deposition_id)
|
|
42
|
+
doi = result.get("doi")
|
|
43
|
+
console.print("[green]✓ Published successfully![/green]")
|
|
44
|
+
console.print(f" [cyan]DOI:[/cyan] {doi}")
|
|
45
|
+
if sandbox:
|
|
46
|
+
console.print(
|
|
47
|
+
"[yellow]Note:[/yellow] This is a sandbox record. Use [bold]--sandbox[/bold] to access it later."
|
|
48
|
+
)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
console.print(f"[red]✗ Failed to publish: {e}[/red]")
|
|
51
|
+
logger.error("Publish failed: %s", e)
|
|
52
|
+
raise typer.Exit(1) from e
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""CLI for uploading files to Zenodo depositions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def upload_command( # pylint: disable=import-outside-toplevel,too-many-locals
|
|
12
|
+
deposition_id: Annotated[str, typer.Argument(help="Deposition ID")],
|
|
13
|
+
files: Annotated[list[str] | None, typer.Option("--file", help="Files to upload (repeat for multiple)")] = None,
|
|
14
|
+
sandbox: Annotated[bool, typer.Option("--sandbox", help="Use sandbox environment")] = False,
|
|
15
|
+
token: Annotated[str | None, typer.Option("--token", help="Zenodo access token")] = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Upload files to a deposition.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
# Single file
|
|
21
|
+
gwsim repository upload 123456 --file data.gwf
|
|
22
|
+
|
|
23
|
+
# Multiple files
|
|
24
|
+
gwsim repository upload 123456 --file data1.gwf --file data2.gwf --file metadata.yaml
|
|
25
|
+
"""
|
|
26
|
+
import logging
|
|
27
|
+
|
|
28
|
+
from rich.console import Console
|
|
29
|
+
from rich.progress import Progress
|
|
30
|
+
|
|
31
|
+
from gwsim.cli.repository.utils import get_zenodo_client
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("gwsim")
|
|
34
|
+
console = Console()
|
|
35
|
+
|
|
36
|
+
if not files:
|
|
37
|
+
console.print("[red]Error:[/red] No files specified. Use [bold]--file <path>[/bold] to specify files.")
|
|
38
|
+
raise typer.Exit(1)
|
|
39
|
+
|
|
40
|
+
client = get_zenodo_client(sandbox=sandbox, token=token)
|
|
41
|
+
|
|
42
|
+
console.print(f"[bold blue]Uploading {len(files)} file(s) to deposition {deposition_id}...[/bold blue]")
|
|
43
|
+
|
|
44
|
+
failed_count = 0
|
|
45
|
+
with Progress() as progress:
|
|
46
|
+
task = progress.add_task("Uploading", total=len(files))
|
|
47
|
+
|
|
48
|
+
for file_path_str in files:
|
|
49
|
+
file_path = Path(file_path_str)
|
|
50
|
+
if not file_path.exists():
|
|
51
|
+
console.print(f"[red]✗ File not found:[/red] {file_path}")
|
|
52
|
+
failed_count += 1
|
|
53
|
+
progress.update(task, advance=1)
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
file_size_mb = file_path.stat().st_size / (1024 * 1024)
|
|
58
|
+
client.upload_file(deposition_id, file_path, auto_timeout=True)
|
|
59
|
+
console.print(f"[green]✓ {file_path.name}[/green] ({file_size_mb:.2f} MB)")
|
|
60
|
+
progress.update(task, advance=1)
|
|
61
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
62
|
+
console.print(f"[red]✗ Failed to upload {file_path.name}:[/red] {e}")
|
|
63
|
+
logger.error("Upload failed for %s: %s", file_path, e)
|
|
64
|
+
failed_count += 1
|
|
65
|
+
progress.update(task, advance=1)
|
|
66
|
+
|
|
67
|
+
if failed_count == 0:
|
|
68
|
+
if sandbox:
|
|
69
|
+
console.print("[cyan]Next:[/cyan] gwsim repository update <id> --metadata-file <file> --sandbox")
|
|
70
|
+
else:
|
|
71
|
+
console.print("[cyan]Next:[/cyan] gwsim repository update <id> --metadata-file <file>")
|
|
72
|
+
else:
|
|
73
|
+
console.print(f"[yellow]Warning:[/yellow] {failed_count} file(s) failed to upload.")
|
|
74
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Utility functions for repository CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from gwsim.repository.zenodo import ZenodoClient
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_zenodo_client(sandbox: bool = False, token: str | None = None) -> ZenodoClient:
|
|
16
|
+
"""Get a ZenodoClient instance with token from env or argument.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
sandbox: Use sandbox environment for testing.
|
|
20
|
+
token: Access token (defaults to ZENODO_TOKEN env var).
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Configured ZenodoClient.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
typer.Exit: If no token is provided or found in environment.
|
|
27
|
+
"""
|
|
28
|
+
if token is None:
|
|
29
|
+
if sandbox:
|
|
30
|
+
token = os.environ.get("ZENODO_SANDBOX_API_TOKEN")
|
|
31
|
+
else:
|
|
32
|
+
token = os.environ.get("ZENODO_API_TOKEN")
|
|
33
|
+
if not token:
|
|
34
|
+
if sandbox:
|
|
35
|
+
console.print(
|
|
36
|
+
"[red]Error:[/red] No Zenodo Sandbox access token provided.\n"
|
|
37
|
+
"Set [bold]ZENODO_SANDBOX_API_TOKEN[/bold] environment variable or use [bold]--token[/bold] option.\n"
|
|
38
|
+
"Get a token from: https://sandbox.zenodo.org/account/settings/applications/tokens/new"
|
|
39
|
+
)
|
|
40
|
+
else:
|
|
41
|
+
console.print(
|
|
42
|
+
"[red]Error:[/red] No Zenodo access token provided.\n"
|
|
43
|
+
"Set [bold]ZENODO_API_TOKEN[/bold] environment variable or use [bold]--token[/bold] option.\n"
|
|
44
|
+
"Get a token from: https://zenodo.org/account/settings/applications/tokens/new"
|
|
45
|
+
)
|
|
46
|
+
raise typer.Exit(1)
|
|
47
|
+
return ZenodoClient(access_token=token, sandbox=sandbox)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""CLI for managing Zenodo repositories."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def verify_command( # pylint: disable=import-outside-toplevel
|
|
11
|
+
sandbox: Annotated[bool, typer.Option("--sandbox", help="Verify sandbox token")] = False,
|
|
12
|
+
token: Annotated[str | None, typer.Option("--token", help="Zenodo access token to verify")] = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
"""Verify that your Zenodo API token is valid and has the correct permissions.
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
# Verify production token
|
|
18
|
+
gwsim repository verify
|
|
19
|
+
|
|
20
|
+
# Verify sandbox token
|
|
21
|
+
gwsim repository verify --sandbox
|
|
22
|
+
|
|
23
|
+
# Verify with explicit token
|
|
24
|
+
gwsim repository verify --token your_token_here
|
|
25
|
+
"""
|
|
26
|
+
from rich.console import Console
|
|
27
|
+
|
|
28
|
+
from gwsim.cli.repository.utils import get_zenodo_client
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
console.print("[bold blue]Verifying Zenodo API token...[/bold blue]")
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
client = get_zenodo_client(sandbox=sandbox, token=token)
|
|
36
|
+
|
|
37
|
+
# Try to list depositions as a test
|
|
38
|
+
console.print("Testing API access...")
|
|
39
|
+
depositions = client.list_depositions(status="draft")
|
|
40
|
+
|
|
41
|
+
env_name = "Zenodo Sandbox" if sandbox else "Zenodo (Production)"
|
|
42
|
+
console.print("[green]✓ Token is valid![/green]")
|
|
43
|
+
console.print(f" [cyan]Environment:[/cyan] {env_name}")
|
|
44
|
+
console.print(f" [cyan]Found {len(depositions)} draft deposition(s)[/cyan]")
|
|
45
|
+
|
|
46
|
+
except Exception as e:
|
|
47
|
+
env_name = "Zenodo Sandbox" if sandbox else "Zenodo (Production)"
|
|
48
|
+
console.print(f"[red]✗ Token verification failed for {env_name}[/red]")
|
|
49
|
+
console.print(f" [yellow]Error:[/yellow] {e}")
|
|
50
|
+
console.print("\n[bold]Troubleshooting:[/bold]")
|
|
51
|
+
if sandbox:
|
|
52
|
+
console.print(
|
|
53
|
+
" 1. Get a new token from: https://sandbox.zenodo.org/account/settings/applications/tokens/new"
|
|
54
|
+
)
|
|
55
|
+
console.print(" 2. Ensure the token has 'deposit:write' and 'deposit:actions' scopes")
|
|
56
|
+
console.print(" 3. Set: export ZENODO_SANDBOX_API_TOKEN='your_token'")
|
|
57
|
+
else:
|
|
58
|
+
console.print(" 1. Get a new token from: https://zenodo.org/account/settings/applications/tokens/new")
|
|
59
|
+
console.print(" 2. Ensure the token has 'deposit:write' and 'deposit:actions' scopes")
|
|
60
|
+
console.print(" 3. Set: export ZENODO_API_TOKEN='your_token'")
|
|
61
|
+
raise typer.Exit(1) from e
|
gwsim/cli/simulate.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A sub-command to handle data generation using simulation plans.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _simulate_impl( # pylint: disable=too-many-locals, too-many-branches, too-many-statements
|
|
13
|
+
config_file_names: str | list[str],
|
|
14
|
+
output_dir: str | None = None,
|
|
15
|
+
metadata_dir: str | None = None,
|
|
16
|
+
overwrite: bool = False,
|
|
17
|
+
metadata: bool = True,
|
|
18
|
+
author: str | None = None,
|
|
19
|
+
email: str | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Internal implementation of simulate command that accepts both str and list[str].
|
|
22
|
+
|
|
23
|
+
Unified approach: All simulation goes through the same plan execution system.
|
|
24
|
+
The difference is only in how we create the initial plan:
|
|
25
|
+
- Config file → SimulationPlan with pre_batch_state=None (fresh simulation)
|
|
26
|
+
- Metadata files → SimulationPlan with pre_batch_state=<dict> (reproduce)
|
|
27
|
+
|
|
28
|
+
Both cases end up calling execute_plan with batches that may or may not have
|
|
29
|
+
state snapshots. The execute_plan function handles both transparently.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
config_file_names: Path to YAML config file OR one or more metadata files
|
|
33
|
+
output_dir: Output directory override
|
|
34
|
+
metadata_dir: Metadata directory override (config mode only)
|
|
35
|
+
overwrite: Whether to overwrite existing files
|
|
36
|
+
metadata: Whether to save metadata files (config mode only)
|
|
37
|
+
author: Author name for metadata
|
|
38
|
+
email: Author email for metadata
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
None
|
|
42
|
+
"""
|
|
43
|
+
import logging # pylint: disable=import-outside-toplevel
|
|
44
|
+
from pathlib import Path # pylint: disable=import-outside-toplevel
|
|
45
|
+
|
|
46
|
+
from gwsim.cli.simulate_utils import ( # pylint: disable=import-outside-toplevel
|
|
47
|
+
execute_plan,
|
|
48
|
+
validate_plan,
|
|
49
|
+
)
|
|
50
|
+
from gwsim.cli.utils.config import load_config # pylint: disable=import-outside-toplevel
|
|
51
|
+
from gwsim.cli.utils.simulation_plan import ( # pylint: disable=import-outside-toplevel
|
|
52
|
+
create_plan_from_config,
|
|
53
|
+
create_plan_from_metadata_files,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
logger = logging.getLogger("gwsim")
|
|
57
|
+
logger.setLevel(logging.DEBUG)
|
|
58
|
+
|
|
59
|
+
checkpoint_dir = Path(".gwsim_checkpoints")
|
|
60
|
+
checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# ===== Normalize input: accept both string and list =====
|
|
64
|
+
if isinstance(config_file_names, str):
|
|
65
|
+
config_file_names_list = [config_file_names]
|
|
66
|
+
else:
|
|
67
|
+
config_file_names_list = config_file_names
|
|
68
|
+
|
|
69
|
+
# ===== Auto-detect mode: Config file vs Metadata files =====
|
|
70
|
+
if len(config_file_names_list) == 1:
|
|
71
|
+
# Single argument: could be YAML config or metadata file
|
|
72
|
+
single_path = Path(config_file_names_list[0])
|
|
73
|
+
is_metadata = (
|
|
74
|
+
single_path.suffix == ".metadata.yaml"
|
|
75
|
+
or (single_path.is_file() and "metadata" in single_path.name)
|
|
76
|
+
or single_path.is_dir() # Directory assumed to be metadata dir
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
# Multiple arguments: must be metadata files
|
|
80
|
+
is_metadata = True
|
|
81
|
+
|
|
82
|
+
# ===== Create plan (unified approach: both modes create same data structure) =====
|
|
83
|
+
if is_metadata:
|
|
84
|
+
logger.info("Reproduction mode: %d metadata file(s)", len(config_file_names_list))
|
|
85
|
+
|
|
86
|
+
metadata_paths = [Path(f) for f in config_file_names_list]
|
|
87
|
+
|
|
88
|
+
# If single directory, load all metadata files from it
|
|
89
|
+
if len(metadata_paths) == 1 and metadata_paths[0].is_dir():
|
|
90
|
+
metadata_dir_path = metadata_paths[0]
|
|
91
|
+
metadata_files = list(metadata_dir_path.glob("*.metadata.yaml"))
|
|
92
|
+
if not metadata_files:
|
|
93
|
+
raise ValueError(f"No metadata files found in directory: {metadata_dir_path}")
|
|
94
|
+
logger.info("Found %d metadata files in directory: %s", len(metadata_files), metadata_dir_path)
|
|
95
|
+
plan = create_plan_from_metadata_files(metadata_files, checkpoint_dir, author=author, email=email)
|
|
96
|
+
else:
|
|
97
|
+
# Individual metadata files
|
|
98
|
+
plan = create_plan_from_metadata_files(metadata_paths, checkpoint_dir, author=author, email=email)
|
|
99
|
+
|
|
100
|
+
logger.info(
|
|
101
|
+
"Created reproduction plan from %d metadata file(s) with %d batches",
|
|
102
|
+
len(config_file_names_list),
|
|
103
|
+
plan.total_batches,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
logger.info("Config mode: %s", config_file_names_list[0])
|
|
107
|
+
config_path = Path(config_file_names_list[0])
|
|
108
|
+
config = load_config(file_name=config_path)
|
|
109
|
+
logger.debug("Configuration loaded successfully from %s", config_file_names_list[0])
|
|
110
|
+
|
|
111
|
+
plan = create_plan_from_config(config, checkpoint_dir, author=author, email=email)
|
|
112
|
+
logger.info("Created simulation plan with %d batches", plan.total_batches)
|
|
113
|
+
|
|
114
|
+
# ===== Determine output directories (same logic for both modes) =====
|
|
115
|
+
# Get reference config from first batch
|
|
116
|
+
if not plan.batches:
|
|
117
|
+
raise ValueError("No batches found in simulation plan")
|
|
118
|
+
|
|
119
|
+
first_batch = plan.batches[0]
|
|
120
|
+
globals_cfg = first_batch.globals_config
|
|
121
|
+
working_dir = Path(globals_cfg.working_directory or ".") # pylint: disable=no-member
|
|
122
|
+
output_dir_config = globals_cfg.output_directory or "output" # pylint: disable=no-member
|
|
123
|
+
|
|
124
|
+
# Handle absolute vs relative paths
|
|
125
|
+
config_output_dir = (
|
|
126
|
+
Path(output_dir_config) if Path(output_dir_config).is_absolute() else working_dir / output_dir_config
|
|
127
|
+
)
|
|
128
|
+
final_output_dir = Path(output_dir) if output_dir else config_output_dir
|
|
129
|
+
|
|
130
|
+
# Metadata directory only used in config mode (fresh simulation)
|
|
131
|
+
final_metadata_dir = None
|
|
132
|
+
if not is_metadata and metadata:
|
|
133
|
+
metadata_dir_config = globals_cfg.metadata_directory or "metadata" # pylint: disable=no-member
|
|
134
|
+
config_metadata_dir = (
|
|
135
|
+
Path(metadata_dir_config)
|
|
136
|
+
if Path(metadata_dir_config).is_absolute()
|
|
137
|
+
else working_dir / metadata_dir_config
|
|
138
|
+
)
|
|
139
|
+
final_metadata_dir = Path(metadata_dir) if metadata_dir else config_metadata_dir
|
|
140
|
+
|
|
141
|
+
logger.debug("Output directory: %s", final_output_dir)
|
|
142
|
+
if final_metadata_dir:
|
|
143
|
+
logger.debug("Metadata directory: %s", final_metadata_dir)
|
|
144
|
+
|
|
145
|
+
# ===== Validate and execute plan =====
|
|
146
|
+
validate_plan(plan)
|
|
147
|
+
logger.info("Simulation plan validation passed")
|
|
148
|
+
|
|
149
|
+
execute_plan(
|
|
150
|
+
plan=plan,
|
|
151
|
+
output_directory=final_output_dir,
|
|
152
|
+
metadata_directory=final_metadata_dir or Path("metadata"),
|
|
153
|
+
overwrite=overwrite,
|
|
154
|
+
max_retries=3,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
logger.info("Simulation completed successfully. Output written to %s", final_output_dir)
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error("Simulation failed: %s", str(e), exc_info=True)
|
|
161
|
+
raise typer.Exit(code=1) from e
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def simulate_command(
|
|
165
|
+
config_file_names: Annotated[
|
|
166
|
+
list[str],
|
|
167
|
+
typer.Argument(help="Configuration file (YAML) or metadata files (can specify multiple metadata files)"),
|
|
168
|
+
],
|
|
169
|
+
output_dir: Annotated[
|
|
170
|
+
str | None, typer.Option("--output-dir", help="Output directory (overrides config/metadata defaults)")
|
|
171
|
+
] = None,
|
|
172
|
+
metadata_dir: Annotated[
|
|
173
|
+
str | None,
|
|
174
|
+
typer.Option(
|
|
175
|
+
"--metadata-dir", help="Metadata directory (overrides config/metadata defaults, only for config mode)"
|
|
176
|
+
),
|
|
177
|
+
] = None,
|
|
178
|
+
overwrite: Annotated[bool, typer.Option("--overwrite", help="Overwrite existing files")] = False,
|
|
179
|
+
metadata: Annotated[bool, typer.Option("--metadata", help="Generate metadata files (only in config mode)")] = True,
|
|
180
|
+
author: Annotated[str | None, typer.Option("--author", help="Author name for the simulation")] = None,
|
|
181
|
+
email: Annotated[str | None, typer.Option("--email", help="Author email for the simulation")] = None,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Generate gravitational wave simulation data using specified simulators.
|
|
184
|
+
|
|
185
|
+
This command can run in two modes, automatically detected from the input:
|
|
186
|
+
|
|
187
|
+
1. **Config Mode**: Provide a single YAML configuration file to create a simulation plan.
|
|
188
|
+
Executes all simulators with state tracking for reproducibility.
|
|
189
|
+
Example: `gwsim simulate config.yaml`
|
|
190
|
+
|
|
191
|
+
2. **Reproduction Mode**: Provide one or more metadata files to reproduce specific batches.
|
|
192
|
+
Each metadata file contains the exact configuration and pre-batch state needed for
|
|
193
|
+
exact reproducibility. Users can distribute individual metadata files, and anyone
|
|
194
|
+
can reproduce those specific batches independently.
|
|
195
|
+
Example: `gwsim simulate signal-0.metadata.yaml signal-1.metadata.yaml`
|
|
196
|
+
|
|
197
|
+
Path Overrides:
|
|
198
|
+
- Use `--output-dir` to specify where output files should be saved
|
|
199
|
+
- Use `--metadata-dir` to specify where metadata should be saved (config mode only)
|
|
200
|
+
- These override paths from the configuration or metadata files
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
config_file_names: Path to YAML config file OR one or more metadata files
|
|
204
|
+
output_dir: Output directory override
|
|
205
|
+
metadata_dir: Metadata directory override (config mode only)
|
|
206
|
+
overwrite: Whether to overwrite existing files
|
|
207
|
+
metadata: Whether to save metadata files (config mode only)
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
None
|
|
211
|
+
"""
|
|
212
|
+
_simulate_impl(
|
|
213
|
+
config_file_names=config_file_names,
|
|
214
|
+
output_dir=output_dir,
|
|
215
|
+
metadata_dir=metadata_dir,
|
|
216
|
+
overwrite=overwrite,
|
|
217
|
+
metadata=metadata,
|
|
218
|
+
author=author,
|
|
219
|
+
email=email,
|
|
220
|
+
)
|