Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d5d693d
Bump version to 0.5.0; update related version references in configura…
carlidel Aug 8, 2025
06a794e
Refactor default_tracker.py to get class definitions directly from Xs…
carlidel Aug 8, 2025
8f00759
Add support for particle monitors!!!
carlidel Aug 8, 2025
63ba42f
Add particle monitor creation and comparison assertions in tests
carlidel Aug 8, 2025
fdf7192
Apply pylint
carlidel Aug 8, 2025
bf39cf0
Apply isort
carlidel Aug 8, 2025
0deeed1
Apply pylint
carlidel Aug 8, 2025
036379d
Enhance README with particle monitor usage and job size limitations
carlidel Aug 8, 2025
2d3dfd4
Merge pull request #3 from carlidel/release/v0.4.1
carlidel Aug 9, 2025
27b18d1
Add I/O buffer support and related functionality to simulation compon…
carlidel Aug 28, 2025
658a2ce
Add io_buffer property and functionality to XbState class
carlidel Aug 28, 2025
0ef67a2
Refactor monitor type checking in XbInput class to use ALLOWED_MONITO…
carlidel Aug 28, 2025
15e7bd9
Merge branch 'main' of https://github.com/carlidel/xboinc into releas…
carlidel Aug 28, 2025
b71fb2d
Merge branch 'main' of https://github.com/carlidel/xboinc into releas…
carlidel Aug 29, 2025
690b7cb
Add 'with_records' parameter to JobSubmitter for tracking information
carlidel Aug 29, 2025
836b522
Refactor I/O buffer comments for clarity in input and output modules
carlidel Aug 29, 2025
3372d26
Fix io_buffer assignment in JobSubmitter for correct tracking informa…
carlidel Aug 29, 2025
ce0816a
Revert xboinc_exec_version to 4 in version.h
carlidel Aug 29, 2025
13d5491
Add test for impact table with and without BOINC integration
carlidel Aug 29, 2025
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
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ import xboinc as xb
# prepare the line
line = xt.Line.from_json("path/to/your/line.json")

# Add monitors in the line if needed
monitor = xt.ParticlesMonitor(...)
line.append("my_monitor", monitor)

# create a job manager
job_manager = xb.JobSubmitter(
user="mycernshortname",
Expand Down Expand Up @@ -107,15 +111,22 @@ job_manager.submit()

Note that the jobs will be executed on a single CPU core from a volunteer computer, we therefore recommend balancing the workload across multiple jobs to optimize the usage of available resources. Xboinc will offer a time estimate for each job, which can help you to decide how many particles to track in each job. Note also that we are currently enforcing a lower time limit of 90 seconds for each job, as it becomes not practical to use the BOINC platform for jobs that take less time than that.

Moreover, to avoid excessive bandwidth/disk usage, we currently limit the size of a single job to 1 GB. Consider this when allocating large monitors in the line, as this has a direct impact on the resulting footprint of the input and output files transmitted.

## Retrieve the results

When the jobs are completed, the Xboinc server will store the results in your allocated folder in compressed tar files. You can decompress and explore them by using the `JobRetriever` class from the `xboinc` package. The simplest way to do that is:

```python
import xboinc as xb

for job_name, job_metadata, result_particles in xb.JobRetriever.iterate("mycernshortname", "a_relevant_study_name", dev_server=True):
print(f"Job {job_name} completed with particles: {result_particles.to_dict()}")
for job_name, job_metadata, result_particles, line_of_monitors, io_buffer in xb.JobRetriever.iterate("mycernshortname", "a_relevant_study_name", dev_server=True):
print(f"Job {job_name} completed!")
# Tracked particles are available directly
print(result_particles.at_turn)
# Monitors are stored in a separate line with only monitors inside
# You can access them by their given name as you would in your original line!
print(line_of_monitors.element_dict["my_monitor"].x)
print(f"Job metadata: {job_metadata}")

```
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"Frederik F. Van der Veken, Carlo E. Montanari, Davide Di Croce, Giovanni Iadorala"
)
copyright = f"2025, {author}"
release = "0.4.1"
release = "0.5.0"
version = release

# -- General configuration ---------------------------------------------------
Expand Down
12 changes: 6 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ include = [ "LICENSE", "NOTICE" ]
python = '>=3.9'
numpy = '>=1.0'
packaging = '>=23.0'
xobjects = ">=0.5.0"
xobjects = '==0.5.2'
xdeps = '==0.10.5'
xpart = '==0.23.0'
xtrack = '==0.84.7'
xfields = '==0.24.0'
xcoll = '==0.6.1'
xpart = '==0.23.1'
xtrack = '==0.88.2'
xfields = '==0.25.1'
xcoll = '==0.6.2'
xaux = '==0.3.5'
xsuite = '==0.32.3'
xsuite = '==0.36.1'

[poetry.group.dev.dependencies]
pytest = ">7"
Expand Down
136 changes: 136 additions & 0 deletions tests/test_01_compilation_and_running.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import os
import subprocess
import time
import pandas as pd
from pathlib import Path
from typing import Optional, Tuple

import numpy as np
import pytest
import xcoll as xc
import xtrack as xt

import xboinc as xb
Expand Down Expand Up @@ -119,6 +121,13 @@ def create_test_particles(
The tracking line and initial particle distribution.
"""
line = xt.Line.from_json(TestConfig.LINE_FILE)
monitor = xt.ParticlesMonitor(
start_at_turn=0,
stop_at_turn=TestConfig.NUM_TURNS,
particle_id_range=(0, 10),
)
line.append("my_monitor", monitor)

line.build_tracker()

x_norm = np.linspace(-15, 15, TestConfig.NUM_PARTICLES)
Expand Down Expand Up @@ -232,8 +241,43 @@ def assert_particles_equal(
for attr in attributes:
values1 = getattr(particles1, attr)
values2 = getattr(particles2, attr)
assert np.array_equal(

values1, values2
), f"{context}: {attr} values are not equal"


def assert_monitors_equal(
monitor1: xt.ParticlesMonitor, monitor2: xt.ParticlesMonitor, context: str
) -> None:
"""
Assert that two particle monitor objects are equivalent.

Parameters
----------
monitor1, monitor2 : xt.ParticlesMonitor
The particle monitor objects to compare.
context : str
Description of the comparison context for error messages.
"""
attributes = [
"particle_id",
"state",
"at_turn",
"x",
"y",
"zeta",
"px",
"py",
"delta",
]

for attr in attributes:
values1 = getattr(monitor1, attr)
values2 = getattr(monitor2, attr)
assert np.array_equal(
values1, values2

), f"{context}: {attr} values are not equal"


Expand Down Expand Up @@ -523,6 +567,11 @@ def test_consistency_with_xtrack(skip_version_check, cleanup_files):
xb_state_boinc.particles,
f"xboinc vs xtrack (at_element={at_element})",
)
assert_monitors_equal(
line.element_dict["my_monitor"],
xb_state_boinc.monitors.element_dict["my_monitor"],
f"xboinc vs xtrack (at_element={at_element})",
)

# Test different stop elements
stop_elements = ["ip2", 3500]
Expand Down Expand Up @@ -568,3 +617,90 @@ def test_consistency_with_xtrack(skip_version_check, cleanup_files):
xb_state.particles,
f"{exec_name} vs xtrack (ele_stop={ele_stop})",
)
assert_monitors_equal(
line.element_dict["my_monitor"],
xb_state.monitors.element_dict["my_monitor"],
f"{exec_name} vs xtrack (ele_stop={ele_stop})",
)


@pytest.mark.parametrize(
"use_boinc",
[
False,
pytest.param(
True,
marks=pytest.mark.skipif(
not TestConfig.vcpkg_available(),
reason="VCPKG + BOINC installation not found",
),
),
],
ids=["w/o BOINC api", "with BOINC api"],
)
def test_impact_table(use_boinc, skip_version_check, cleanup_files):
# Get line and collimators
line = xt.Line.from_json(xb._pkg_root.parent / 'tests' / 'data' / 'xcoll' / 'lhc_run3_b1.json')
colldb = xc.CollimatorDatabase.from_yaml(xb._pkg_root.parent / 'tests' / 'data' / 'xcoll' / 'lhc_run3.yaml', beam=1)
colldb.install_everest_collimators(verbose=True, line=line)
df_with_coll = line.check_aperture()
assert not np.any(df_with_coll.has_aperture_problem)
copy_line = line.copy()

# Start interaction record
impacts = xc.InteractionRecord.start(line=line)
copy_impacts = xc.InteractionRecord.start(line=copy_line)

# Build tracker, assign optics and generate particles
line.build_tracker()
copy_line.build_tracker()

# Final touches...
line.collimators.assign_optics()
part = line['tcp.d6l7.b1'].generate_pencil(5000)
line.scattering.enable()

# Create input file
input_file = Path.cwd() / TestConfig.INPUT_FILE
xb_input = xb.XbInput(
line=line,
particles=part,
num_turns=20,
checkpoint_every=100,
io_buffer=line.tracker.io_buffer,
)
xb_input.to_binary(input_file)

# Track with Xsuite
line.track(part, num_turns=20)
ref_df = impacts.to_pandas()
blank_df = copy_impacts.to_pandas()

# They should NOT be equal!!!
assert not ref_df.equals(blank_df)

# Track with Xboinc
executable_test = get_executable_path(use_boinc=False)
run_xboinc_tracking(executable_test)
output_file = Path.cwd() / TestConfig.OUTPUT_FILE
xb_state = xb.XbState.from_binary(output_file)

# Inject xb_state io_buffer into new line
xb_state.place_io_buffer(copy_line)
xb_df = copy_impacts.to_pandas()

# Now, these two DataFrames should be equal
pd.testing.assert_frame_equal(ref_df, xb_df)

# If VCPKG is available, re-run the test with BOINC
if TestConfig.vcpkg_available():
# Track with Xboinc
executable_test = get_executable_path(use_boinc=True)
run_xboinc_tracking(executable_test)
output_file = Path.cwd() / TestConfig.OUTPUT_FILE
xb_state = xb.XbState.from_binary(output_file)

# Inject xb_state io_buffer into new line
xb_state.place_io_buffer(copy_line)
xb_df = copy_impacts.to_pandas()
pd.testing.assert_frame_equal(ref_df, xb_df)
21 changes: 13 additions & 8 deletions tests/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
# Copyright (c) CERN, 2025. #
########################################### #

import pytest
import sys

import pytest

from xboinc import __version__, __xsuite__versions__
from xboinc.simulation_io import XbVersion
from xboinc.simulation_io.version import _version_to_int, _int_to_version, assert_versions
from xboinc.simulation_io.version import (
_int_to_version,
_version_to_int,
assert_versions,
)


def test_version():
Expand All @@ -25,13 +30,13 @@ def test_xb_ver():

def test_xsuite_versions():
expected_version = {
'xobjects' : '0.5.0',
'xobjects' : '0.5.2',
'xdeps' : '0.10.5',
'xpart' : '0.23.0',
'xtrack' : '0.84.7',
'xfields' : '0.24.0',
'xcoll' : '0.6.1',
'xaux' : '0.3.5'
'xpart' : '0.23.1',
'xtrack' : '0.88.2',
'xfields' : '0.25.1',
'xcoll' : '0.6.2',
'xaux' : '0.3.5',
}
current_version = {}

Expand Down
2 changes: 1 addition & 1 deletion xboinc/executable/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# ################################################################################

cmake_minimum_required(VERSION 3.16)
project(xboinc VERSION 0.4.0 LANGUAGES C CXX)
project(xboinc VERSION 0.5.0 LANGUAGES C CXX)

# === Compiler and Build Options ===
set(CMAKE_C_STANDARD 99)
Expand Down
Loading