Skip to content

Conversation

@digvijay-y
Copy link
Contributor

@digvijay-y digvijay-y commented Dec 17, 2025

Pull Request Checklist for MontePy

Description

Added a _collection_ref as a weak_ref which points the object back to its collection.

Problems

deepcopy got messed up link "number already in use" another failed test due to surface conflicts.
_cell pointed to wrong cell objects.

Solution:

Rebuild cache after deepcopy instead of overwriting _collection_ref.
Added _set_cell method to halfspace

    100 objects -> 0.127 seconds
    500 objects -> 0.443 seconds
   1000 objects -> 0.937 seconds
   8000 objects -> 8.385 seconds
Details
import time
import montepy


class Collection(montepy.Cells):
  """Use the real Cells collection for testing"""

  pass


def dump_stats(n):
  collection = Collection()
  start = time.time()
  for i in range(n):
      cell = montepy.Cell(f"{i+1} 0 -1")
      collection.append_renumber(cell)
  end = time.time()
  print(f"{n:>7d} objects -> {end - start:.3f} seconds")


if __name__ == "__main__":
  print("Performance test: append_renumber with _collection weakrefs")
  print("=" * 60)
  for n_cells in [100, 500, 1000, 8000]:
      dump_stats(n_cells)
  print("=" * 60)

Fixes #849


General Checklist

  • I have performed a self-review of my own code.
  • The code follows the standards outlined in the development documentation.
  • I have formatted my code with black version 25.
  • I have added tests that prove my fix is effective or that my feature works (if applicable).

LLM Disclosure

Were any large language models (LLM or "AI") used in to generate any of this code?

  • Yes
    • Model(s) used:
    • Copilot
  • No

Documentation Checklist

  • I have documented all added classes and methods.
  • For infrastructure updates, I have updated the developer's guide.
  • For significant new features, I have added a section to the getting started guide.

First-Time Contributor Checklist

  • If this is your first contribution, add yourself to pyproject.toml if you wish to do so.

Additional Notes for Reviewers

Ensure that:

  • This PR fully addresses and resolves the referenced issue(s).
  • The submitted code is consistent with the merge checklist outlined here.
  • The PR covers all relevant aspects according to the development guidelines.
  • 100% coverage of the patch is achieved, or justification for a variance is given.

📚 Documentation preview 📚: https://montepy--867.org.readthedocs.build/en/867/

@digvijay-y digvijay-y changed the title Weakref : Collection_ref Implement _collection_ref to link objects to their NumberedObjectCollection parent Dec 17, 2025
@MicahGale
Copy link
Collaborator

Thanks for doing this. I'll probably look at this in the next few days.

Copy link
Collaborator

@MicahGale MicahGale left a comment

Choose a reason for hiding this comment

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

I didn't fully evaluate how this works, but I think there are some design changes needed first.

@digvijay-y
Copy link
Contributor Author

digvijay-y commented Jan 5, 2026

// Ipynd files got updated with black

  1. Moved _collection_ref to Numbered_MCNP_Object
  2. Removed _set_cell methods
  3. Fixed _number_validator to raise error
  4. Created _link_to_collection method
  5. Removed cache rebuild
  6. Fixed try block error message

@digvijay-y
Copy link
Contributor Author

Append to Collection:

Objects Time (s)
100 0.0003
200 0.0006
500 0.0014
1000 0.0030
4000 0.0121
8000 0.0448

Append with Renumber:

Objects Time (s)
100 0.0005
200 0.0010
500 0.0027
1000 0.0055
4000 0.0224
8000 0.0434

Number Changes (500 ops):

Objects Time (s)
100 0.0048
200 0.0041
500 0.0043
1000 0.0043

Deepcopy Problem:

Objects Time (s)
100 0.3575
200 0.4597
500 1.4791
1000 2.8051
4000 11.0377
8000 21.7734
Details
import copy
import time
import montepy


def benchmark_append_to_collection(n_objects: int) -> float:
    """Benchmark appending N objects to a fresh collection."""
    collection = montepy.Cells()
    cells = [montepy.Cell(f"{i+1} 0 -1") for i in range(n_objects)]

    start = time.perf_counter()
    for cell in cells:
        collection.append(cell)
    return time.perf_counter() - start


def benchmark_append_renumber(n_objects: int) -> float:
    """Benchmark appending N objects with renumbering."""
    collection = montepy.Cells()
    cells = [montepy.Cell(f"{i+1} 0 -1") for i in range(n_objects)]

    start = time.perf_counter()
    for cell in cells:
        collection.append_renumber(cell)
    return time.perf_counter() - start


def benchmark_number_changes(n_objects: int) -> float:
    """Benchmark changing object numbers."""
    problem = montepy.read_input("tests/inputs/test.imcnp")
    base_num = 10000
    for i in range(n_objects):
        cell = montepy.Cell(f"{base_num + i} 0 -1")
        problem.cells.append(cell)

    cells = list(problem.cells)
    start = time.perf_counter()
    for i in range(500):
        cell = cells[i % len(cells)]
        old_num = cell.number
        cell.number = old_num + 100000
        cell.number = old_num
    return time.perf_counter() - start


def benchmark_deepcopy(n_objects: int) -> float:
    """Benchmark deepcopy of a problem."""
    problem = montepy.read_input("tests/inputs/test.imcnp")
    for i in range(n_objects):
        cell = montepy.Cell(f"{1000 + i} 0 -1")
        problem.cells.append(cell)

    start = time.perf_counter()
    _ = copy.deepcopy(problem)
    return time.perf_counter() - start


def print_table(name: str, sizes: list[int], benchmark_func):
    """Run benchmark and print simple table."""
    print(f"\n{name}:")
    print(f"{'Objects':<10} | {'Time (s)':<12}")
    print(f"{'-'*10} | {'-'*12}")
    
    for size in sizes:
        elapsed = benchmark_func(size)
        print(f"{size:<10} | {elapsed:<12.4f}")


def main():
    print("=" * 40)
    print("MontePy _collection_ref Benchmark")
    print("=" * 40)

    sizes = [100, 200, 500, 1000, 4000, 8000]
    small_sizes = [100, 200, 500, 1000]

    print_table("Append to Collection", sizes, benchmark_append_to_collection)
    print_table("Append with Renumber", sizes, benchmark_append_renumber)
    print_table("Number Changes (500 ops)", small_sizes, benchmark_number_changes)
    print_table("Deepcopy Problem", sizes, benchmark_deepcopy)
    
    # Sanity check
    print(f"\n{'='*40}")
    print("Sanity Check:")
    problem = montepy.read_input("tests/inputs/test.imcnp")
    cell = list(problem.cells)[0]
    if hasattr(cell, "_collection_ref") and cell._collection is not None:
        print(f"  ✓ _collection_ref is working")
    else:
        print(f"  ✗ _collection_ref is NOT set")
    print("=" * 40)


if __name__ == "__main__":
    main()

@digvijay-y digvijay-y requested a review from MicahGale January 5, 2026 13:59
Copy link
Collaborator

@MicahGale MicahGale left a comment

Choose a reason for hiding this comment

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

This is a big improvement, and much more like what I was thinking. I have some more feedback, but it is not major.

Looking at the data you collected this does look like it is indeed linear (O(N)), so that's great that it is no longer O(N^2). I have been thinking more about how to present performance data recently. I think in general, rates should generally be used (e.g., objects/second) over just raw time. My last paper on this though did not use rates, so I'm working on this as well.

@digvijay-y
Copy link
Contributor Author

I can try data in object/second

@digvijay-y digvijay-y requested a review from MicahGale January 9, 2026 15:05
@digvijay-y
Copy link
Contributor Author

Append to Collection:

Objects Time (s)
100 0.0006
200 0.0010
500 0.0073
1000 0.0034
4000 0.0136
8000 0.0342

Append with Renumber:

Objects Time (s)
100 0.0006
200 0.0011
500 0.0028
1000 0.0057
4000 0.0383
8000 0.0456

Number Changes (500 ops):

Objects Time (s)
100 0.0038
200 0.0038
500 0.0075
1000 0.0042

Deepcopy Problem:

Objects Time (s)
100 0.4294
200 0.5464
500 1.3516
1000 2.9856
4000 12.2528
8000 24.0162

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link objects to their NumberedObjectCollection Parent

2 participants