Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include juliapkg.json
40 changes: 40 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# testparticle-jl

Python wrapper for [TestParticle.jl](https://github.com/JuliaSpacePhysics/TestParticle.jl) - trace charged particles in electromagnetic fields via Julia.

## Installation

```bash
pip install testparticle-jl
```

The Julia package installs automatically on first use.

## Usage

```python
import testparticle as tp
from juliacall import Main as jl
import numpy as np

# Initialize a proton
proton = tp.Proton

# Define E and B fields (simple uniform fields for demonstration)
def get_B(x):
return [0.0, 0.0, 1e-8] # 10 nT in z-direction

def get_E(x):
return [0.0, 0.0, 0.0]

# Prepare the trace problem
# Note: Complex setup might require using Julia types directly via `jl` or helper functions
# This is a placeholder for actual usage logic dependent on TestParticle.jl API
```
Comment on lines +30 to +33

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The usage example is currently a placeholder and doesn't show how to perform a particle trace. To improve usability for new users, please provide a complete, minimal working example that demonstrates a call to one of the trace functions and shows what the output looks like. This would be much more helpful than the current placeholder.


See the [TestParticle.jl documentation](https://juliaspacephysics.github.io/TestParticle.jl/dev/) for full details on the underlying Julia functions.

## API

Available functions mirror the Julia package.
Functions ending with `!` in Julia are available with `_b` suffix (e.g., `trace!` -> `trace_b`).
9 changes: 9 additions & 0 deletions python/juliapkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"julia": "1.10",
"packages": {
"TestParticle": {
"uuid": "953b605b-f162-4481-8f7f-a191c2bb40e3",
"path": ".."
}
}
Comment on lines +3 to +8

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The "path": ".." configuration is suitable for local development but will cause installation to fail for users who install this package from PyPI. When a user installs testparticle-jl, juliacall will try to find the Julia package at a relative path .. which won't exist on their system. To make this package distributable, please replace the path with a version since TestParticle.jl is in the General registry.

Suggested change
"packages": {
"TestParticle": {
"uuid": "953b605b-f162-4481-8f7f-a191c2bb40e3",
"path": ".."
}
}
"packages": {
"TestParticle": {
"uuid": "953b605b-f162-4481-8f7f-a191c2bb40e3",
"version": "0.18.6"
}
}

}
29 changes: 29 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[project]
name = "testparticle-jl"
version = "0.1.0"
description = "Python wrapper for TestParticle.jl - trace charged particles in electromagnetic fields via Julia"
readme = "README.md"
license = "MIT"
requires-python = ">=3.9"
keywords = ["test-particle", "julia", "space-physics", "plasma-physics"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3",
"Topic :: Scientific/Engineering :: Physics",
]
dependencies = ["juliacall>=0.9.20"]

[project.urls]
Homepage = "https://henry2004y.github.io/TestParticle.jl/dev/"
Repository = "https://github.com/henry2004y/TestParticle.jl"

[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"

[tool.pdm.build]
includes = ["src/testparticle"]

[tool.pytest.ini_options]
testpaths = ["tests"]
110 changes: 110 additions & 0 deletions python/src/testparticle/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Python wrapper for TestParticle.jl."""

from juliacall import Main as jl

# Load the Julia package
jl.seval("using TestParticle")

# Re-export Julia functions and types
prepare = jl.TestParticle.prepare
prepare_gc = jl.TestParticle.prepare_gc
get_gc = jl.TestParticle.get_gc
get_gc_func = jl.TestParticle.get_gc_func

# Functions with ! in Julia are accessed with _b suffix in Python via juliacall
trace_b = jl.TestParticle.trace_b
trace_relativistic_b = jl.TestParticle.trace_relativistic_b
trace_normalized_b = jl.TestParticle.trace_normalized_b
trace_relativistic_normalized_b = jl.TestParticle.trace_relativistic_normalized_b

trace = jl.TestParticle.trace
trace_relativistic = jl.TestParticle.trace_relativistic
trace_normalized = jl.TestParticle.trace_normalized
trace_relativistic_normalized = jl.TestParticle.trace_relativistic_normalized

trace_gc_b = jl.TestParticle.trace_gc_b
trace_gc_drifts_b = jl.TestParticle.trace_gc_drifts_b
trace_gc_flr_b = jl.TestParticle.trace_gc_flr_b
trace_gc_exb_b = jl.TestParticle.trace_gc_exb_b
trace_fieldline_b = jl.TestParticle.trace_fieldline_b
trace_fieldline = jl.TestParticle.trace_fieldline

get_gc_velocity = jl.TestParticle.get_gc_velocity
full_to_gc = jl.TestParticle.full_to_gc
gc_to_full = jl.TestParticle.gc_to_full

Proton = jl.TestParticle.Proton
Electron = jl.TestParticle.Electron
Ion = jl.TestParticle.Ion

Maxwellian = jl.TestParticle.Maxwellian
BiMaxwellian = jl.TestParticle.BiMaxwellian
Kappa = jl.TestParticle.Kappa
BiKappa = jl.TestParticle.BiKappa

AdaptiveBoris = jl.TestParticle.AdaptiveBoris
AdaptiveHybrid = jl.TestParticle.AdaptiveHybrid

CurrentLoop = jl.TestParticle.CurrentLoop
getB_loop = jl.TestParticle.getB_loop

get_gyrofrequency = jl.TestParticle.get_gyrofrequency
get_gyroperiod = jl.TestParticle.get_gyroperiod
get_gyroradius = jl.TestParticle.get_gyroradius
get_velocity = jl.TestParticle.get_velocity
get_energy = jl.TestParticle.get_energy
get_mean_magnitude = jl.TestParticle.get_mean_magnitude
energy2velocity = jl.TestParticle.energy2velocity
get_curvature_radius = jl.TestParticle.get_curvature_radius
get_adiabaticity = jl.TestParticle.get_adiabaticity

sample_unit_sphere = jl.TestParticle.sample_unit_sphere
get_number_density_flux = jl.TestParticle.get_number_density_flux

getB_zpinch = jl.TestParticle.getB_zpinch
getB_bottle = jl.TestParticle.getB_bottle
getB_mirror = jl.TestParticle.getB_mirror
getB_tokamak_coil = jl.TestParticle.getB_tokamak_coil

orbit = jl.TestParticle.orbit
monitor = jl.TestParticle.monitor

get_fields = jl.TestParticle.get_fields
get_work = jl.TestParticle.get_work

LazyTimeInterpolator = jl.TestParticle.LazyTimeInterpolator

TraceProblem = jl.TestParticle.TraceProblem
TraceGCProblem = jl.TestParticle.TraceGCProblem
TraceHybridProblem = jl.TestParticle.TraceHybridProblem

CartesianGrid = jl.TestParticle.CartesianGrid
RectilinearGrid = jl.TestParticle.RectilinearGrid
StructuredGrid = jl.TestParticle.StructuredGrid


__version__ = "0.1.0"

__all__ = [
"prepare", "prepare_gc", "get_gc", "get_gc_func",
"trace_b", "trace_relativistic_b", "trace_normalized_b", "trace_relativistic_normalized_b",
"trace", "trace_relativistic", "trace_normalized", "trace_relativistic_normalized",
"trace_gc_b",
"trace_gc_drifts_b", "trace_gc_flr_b", "trace_gc_exb_b", "trace_fieldline_b", "trace_fieldline",
"get_gc_velocity", "full_to_gc", "gc_to_full",
"Proton", "Electron", "Ion",
"Maxwellian", "BiMaxwellian", "Kappa", "BiKappa",
"AdaptiveBoris", "AdaptiveHybrid",
"CurrentLoop", "getB_loop",
"get_gyrofrequency",
"get_gyroperiod", "get_gyroradius", "get_velocity", "get_energy", "get_mean_magnitude",
"energy2velocity", "get_curvature_radius", "get_adiabaticity",
"sample_unit_sphere", "get_number_density_flux",
"getB_zpinch", "getB_bottle", "getB_mirror", "getB_tokamak_coil",
"orbit", "monitor",
"get_fields", "get_work",
"LazyTimeInterpolator",
"TraceProblem", "TraceGCProblem", "TraceHybridProblem",
"CartesianGrid", "RectilinearGrid", "StructuredGrid",
"__version__", "jl"
]
Comment on lines +88 to +110

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The __all__ list is quite long and manually maintained, which can be prone to errors and omissions as the wrapped Julia API evolves. While acceptable, you might consider generating this list dynamically to improve maintainability. For example, you could iterate over dir(jl.TestParticle) and filter for public functions/types to build the list and perform the assignments.

29 changes: 29 additions & 0 deletions python/tests/test_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import pytest

def test_import():
import testparticle as tp
assert hasattr(tp, "trace")
assert hasattr(tp, "trace_b")
assert hasattr(tp, "Proton")
assert hasattr(tp, "Electron")

def test_basic_types():
import testparticle as tp
from juliacall import Main as jl

# Check constants
m_p = tp.Proton.mass
q_p = tp.Proton.charge
assert m_p > 0
assert q_p > 0

def test_gyrofrequency():
import testparticle as tp

# B = 1.0 (scalar or magnitude), q = 1.0, m = 1.0
# The signature in Julia is usually get_gyrofrequency(B, species) or (B, q, m)
# Let's check (B, q, m)

omega = tp.get_gyrofrequency(1.0, 1.0, 1.0)
assert abs(omega - 1.0) < 1e-6

Comment on lines +20 to +29

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current tests are a good start, but they only cover basic type checks and one utility function. The core functionality of the wrapper, which is particle tracing via the trace functions, is not tested. Please add a test case for at least one of the trace functions. This will increase confidence in the wrapper's correctness and also serve as a valuable usage example for developers and users.

Loading