ReceiptsRecorded runs, model-free verdicts
VERIFIED

rhayes777__pyautofit-888

VERIFIED on first verdict

1 attempt, 0 appeals. 1 file, 314 added and 300 removed lines. Tokens: 565,974 tokens. Model cost: $0.04. Sandbox time: 2 min 36 s.

The plan

Repository
rhayes777/pyautofit
Base commit
b0ec41b (image tag)
Issue
Bug where sensitivity mapping no longer supports tuple step_size In a previous issue, we made it so that the `number_of_steps` in the `Sensitivity` class could be a tuple so that different dimensions had different steps: ``` sensitivity = af.Sensitivity( paths=paths, simulation_instance=simulation_instance,
Files to edit
none
Approach
Modify the `_perturb_models` property to handle tuple step_size by computing half_step per dimension, or normalize step_size to always be a float by using the first dimension's step when it's a tuple. Looking at the tests, they expect 16 jobs/models with tuple (2,2,4) steps, so the code needs to iterate over dimensions properly. Planner dropped nonexistent files: ["autofit/non_linear/grid/sensitivity.py"]

Attempt 1

VERIFIED

Agent branch

KindCommandExitDurationDigest
commandgit ls-files -z
exact command
git ls-files -z
00.004s9210ff04
commandfind /PyAutoFit -name "sensitivity.py" 2>/dev/null
exact command
find /PyAutoFit -name "sensitivity.py" 2>/dev/null
00.009s7692297f
commandfind /PyAutoFit -path "*/grid/sensitivity.py" 2>/dev/null
exact command
find /PyAutoFit -path "*/grid/sensitivity.py" 2>/dev/null
00.010sd93f72dc
commandls -la /PyAutoFit/autofit/non_linear/grid/ 2>/dev/null || find /PyAutoFit -type d -name "grid" 2>/dev/null
exact command
ls -la /PyAutoFit/autofit/non_linear/grid/ 2>/dev/null || find /PyAutoFit -type d -name "grid" 2>/dev/null
00.004sbf179fc7
commandls -la /PyAutoFit/autofit/non_linear/grid/sensitivity/
exact command
ls -la /PyAutoFit/autofit/non_linear/grid/sensitivity/
00.004s6636297a
commandfind /PyAutoFit -name "sensitivity.py" -type f 2>/dev/null
exact command
find /PyAutoFit -name "sensitivity.py" -type f 2>/dev/null
00.008se8e7a1c2
commandls -la /PyAutoFit/autofit/non_linear/grid/
exact command
ls -la /PyAutoFit/autofit/non_linear/grid/
00.004s862fca3e
commandls -la /PyAutoFit/autofit/non_linear/grid/sensitivity/
exact command
ls -la /PyAutoFit/autofit/non_linear/grid/sensitivity/
00.004s1486e3aa
show all 49 steps
KindCommandExitDurationDigest
commandfind /PyAutoFit -name "*.py" | xargs grep -l "class Sensitivity" 2>/dev/null
exact command
find /PyAutoFit -name "*.py" | xargs grep -l "class Sensitivity" 2>/dev/null
00.023s4de9194c
commandhead -100 /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
head -100 /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s7f3385fe
commandwc -l /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
wc -l /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s70aa53fe
commandsed -n '280,310p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '280,310p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s03bd5403
commandsed -n '200,280p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '200,280p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s9d8dd5e2
commandsed -n '1,100p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '1,100p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s01ece0fa
commandgrep -n "step_size" /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
grep -n "step_size" /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s4b4c72ed
commandsed -n '170,200p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '170,200p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s92c728f6
commandfind /PyAutoFit -path "*/test*sensitivity*" -name "*.py" 2>/dev/null
exact command
find /PyAutoFit -path "*/test*sensitivity*" -name "*.py" 2>/dev/null
00.009s65a27db2
commandgrep -n "tuple_steps\|test_models_with_tuple_steps\|test_jobs_with_tuple_steps" /PyAutoFit/test_autofit/non_linear/grid/test_sensitivity/test_functionality.py
exact command
grep -n "tuple_steps\|test_models_with_tuple_steps\|test_jobs_with_tuple_steps" /PyAutoFit/test_autofit/non_linear/grid/test_sensitivity/test_functionality.py
00.003s8cbb6222
commandsed -n '130,160p' /PyAutoFit/test_autofit/non_linear/grid/test_sensitivity/test_functionality.py
exact command
sed -n '130,160p' /PyAutoFit/test_autofit/non_linear/grid/test_sensitivity/test_functionality.py
00.003s883fb49e
commandgrep -n "def make_lists" /PyAutoFit/autofit/non_linear/grid/grid_search.py
exact command
grep -n "def make_lists" /PyAutoFit/autofit/non_linear/grid/grid_search.py
20.003s8c7ac47f
commandfind /PyAutoFit -name "grid_search.py" -type f
exact command
find /PyAutoFit -name "grid_search.py" -type f
00.009sc6b4f53b
commandfind /PyAutoFit -path "*/grid_search*" -name "*.py" type f
exact command
find /PyAutoFit -path "*/grid_search*" -name "*.py" type f
10.003sb7aa05de
commandfind /PyAutoFit -name "grid_search.py"
exact command
find /PyAutoFit -name "grid_search.py"
00.009sdf36eaf3
commandls -la /PyAutoFit/autofit/non_linear/grid/grid_search/ 2>/dev/null
exact command
ls -la /PyAutoFit/autofit/non_linear/grid/grid_search/ 2>/dev/null
00.004s095feb8f
commandgrep -n "def make_lists" /PyAutoFit/autofit/non_linear/grid/grid_search/__init__.py
exact command
grep -n "def make_lists" /PyAutoFit/autofit/non_linear/grid/grid_search/__init__.py
00.003sa05b4261
commandsed -n '378,430p' /PyAutoFit/autofit/non_linear/grid/grid_search/__init__.py
exact command
sed -n '378,430p' /PyAutoFit/autofit/non_linear/grid/grid_search/__init__.py
00.003sc5ba23e6
commandsed -n '230,260p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '230,260p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s9ba25e65
commandsed -n '244,262p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '244,262p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s7fdc9e70
commandsed -n '244,270p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '244,270p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s51fda598
commandsed -n '244,262p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py | cat -A
exact command
sed -n '244,262p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py | cat -A
00.005s3eea97fb
commandsed -i '253s/.*/ if isinstance(self.step_size, tuple):\n half_step = tuple(\n self.limit_scale * step / 2 for step in self.step_size\n )\n else:\n half_step = self.limit_scale * self.step_size / 2/' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -i '253s/.*/        if isinstance(self.step_size, tuple):\n            half_step = tuple(\n                self.limit_scale * step / 2 for step in self.step_size\n        )\n        else:\n            half_step = self.limit_scale * self.step_size / 2/' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
10.003sdcaac885
commandpython3 << 'EOF' with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'r') as f: lines = f.readlines() # Find the line with "half_step = self.limit_scale * self.step_size / 2" # It should be around line 253 (0-indexed: 252) for i, line in enumerate(lines): if 'half_step = self.limit_scale * self.step_size / 2' in line: print(f"Found at line {i+1}: {repr(line)}") # Replace this line and the next few lines # Original lines 253-261 (1-indexed): # 253: half_step = self.limit_scale * self.step_size / 2 # 254: for list_ in self._lists: # 255: limits = [ # 256: ( # 257: prior.value_for(max(0.0, centre - half_step)), # 258: prior.value_for(min(1.0, centre + half_step)), # 259: ) # 260: for centre, prior in zip(list_, self.perturb_model.priors_ordered_by_id) # 261: ] # 262: yield self.perturb_model.with_limits(limits) new_code = ''' if isinstance(self.step_size, tuple): half_step = tuple( self.limit_scale * step / 2 for step in self.step_size ) else: half_step = self.limit_scale * self.step_size / 2 for list_ in self._lists: limits = [ ( prior.value_for(max(0.0, centre - half_step[i])), prior.value_for(min(1.0, centre + half_step[i])), ) for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id)) ] yield self.perturb_model.with_limits(limits)''' # Replace lines 253-262 (0-indexed: 252-261) lines[252:262] = [new_code + '\n'] break with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'w') as f: f.writelines(lines) print("Done!") EOF
exact command
python3 << 'EOF'
with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'r') as f:
    lines = f.readlines()

# Find the line with "half_step = self.limit_scale * self.step_size / 2"
# It should be around line 253 (0-indexed: 252)
for i, line in enumerate(lines):
    if 'half_step = self.limit_scale * self.step_size / 2' in line:
        print(f"Found at line {i+1}: {repr(line)}")
        # Replace this line and the next few lines
        # Original lines 253-261 (1-indexed):
        # 253:         half_step = self.limit_scale * self.step_size / 2
        # 254:         for list_ in self._lists:
        # 255:             limits = [
        # 256:                 (
        # 257:                     prior.value_for(max(0.0, centre - half_step)),
        # 258:                     prior.value_for(min(1.0, centre + half_step)),
        # 259:                 )
        # 260:                 for centre, prior in zip(list_, self.perturb_model.priors_ordered_by_id)
        # 261:             ]
        # 262:             yield self.perturb_model.with_limits(limits)
        
        new_code = '''        if isinstance(self.step_size, tuple):
            half_step = tuple(
                self.limit_scale * step / 2 for step in self.step_size
            )
        else:
            half_step = self.limit_scale * self.step_size / 2
        for list_ in self._lists:
            limits = [
                (
                    prior.value_for(max(0.0, centre - half_step[i])),
                    prior.value_for(min(1.0, centre + half_step[i])),
                )
                for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id))
            ]
            yield self.perturb_model.with_limits(limits)'''
        
        # Replace lines 253-262 (0-indexed: 252-261)
        lines[252:262] = [new_code + '\n']
        break

with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'w') as f:
    f.writelines(lines)

print("Done!")
EOF
00.023s2b00bea4
commandsed -n '250,275p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '250,275p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s3520a72f
commandsed -n '244,280p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '244,280p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003sb06bb143
commandgit -C /PyAutoFit checkout -- autofit/non_linear/grid/sensitivity/__init__.py 2>/dev/null || echo "Not a git repo or file not tracked"
exact command
git -C /PyAutoFit checkout -- autofit/non_linear/grid/sensitivity/__init__.py 2>/dev/null || echo "Not a git repo or file not tracked"
00.005s5c4017fd
commandgit -C /PyAutoFit status 2>&1 | head -20
exact command
git -C /PyAutoFit status 2>&1 | head -20
00.021s725c9092
commandgit -C /PyAutoFit log --oneline -5
exact command
git -C /PyAutoFit log --oneline -5
00.004s6f25794a
commandgit -C /PyAutoFit restore autofit/non_linear/grid/sensitivity/__init__.py
exact command
git -C /PyAutoFit restore autofit/non_linear/grid/sensitivity/__init__.py
00.005s0e7a26b3
commandsed -n '244,265p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '244,265p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003sb9420961
commandpython3 << 'PYEOF' with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'r') as f: content = f.read() # The old code to replace old_code = ''' half_step = self.limit_scale * self.step_size / 2 for list_ in self._lists: limits = [ ( prior.value_for(max(0.0, centre - half_step)), prior.value_for(min(1.0, centre + half_step)), ) for centre, prior in zip(list_, self.perturb_model.priors_ordered_by_id) ] yield self.perturb_model.with_limits(limits)''' # The new code new_code = ''' if isinstance(self.step_size, tuple): half_step = tuple( self.limit_scale * step / 2 for step in self.step_size ) else: half_step = self.limit_scale * self.step_size / 2 for list_ in self._lists: limits = [ ( prior.value_for(max(0.0, centre - half_step[i])), prior.value_for(min(1.0, centre + half_step[i])), ) for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id)) ] yield self.perturb_model.with_limits(limits)''' if old_code in content: new_content = content.replace(old_code, new_code) with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'w') as f: f.write(new_content) print("Successfully replaced the code") else: print("Could not find the old code pattern") # Let's debug what's actually there idx = content.find('half_step = self.limit_scale * self.step_size / 2') if idx != -1: print(f"Found at index {idx}") print(repr(content[idx:idx+200])) PYEOF
exact command
python3 << 'PYEOF'
with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'r') as f:
    content = f.read()

# The old code to replace
old_code = '''        half_step = self.limit_scale * self.step_size / 2
        for list_ in self._lists:
            limits = [
                (
                    prior.value_for(max(0.0, centre - half_step)),
                    prior.value_for(min(1.0, centre + half_step)),
                )
                for centre, prior in zip(list_, self.perturb_model.priors_ordered_by_id)
            ]
            yield self.perturb_model.with_limits(limits)'''

# The new code
new_code = '''        if isinstance(self.step_size, tuple):
            half_step = tuple(
                self.limit_scale * step / 2 for step in self.step_size
            )
        else:
            half_step = self.limit_scale * self.step_size / 2
        for list_ in self._lists:
            limits = [
                (
                    prior.value_for(max(0.0, centre - half_step[i])),
                    prior.value_for(min(1.0, centre + half_step[i])),
                )
                for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id))
            ]
            yield self.perturb_model.with_limits(limits)'''

if old_code in content:
    new_content = content.replace(old_code, new_code)
    with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'w') as f:
        f.write(new_content)
    print("Successfully replaced the code")
else:
    print("Could not find the old code pattern")
    # Let's debug what's actually there
    idx = content.find('half_step = self.limit_scale * self.step_size / 2')
    if idx != -1:
        print(f"Found at index {idx}")
        print(repr(content[idx:idx+200]))
PYEOF
00.025s7300eb4b
commandsed -n '244,275p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '244,275p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s7a74891c
commandcd /PyAutoFit && python -m pytest test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_models_with_tuple_steps test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_jobs_with_tuple_steps -v 2>&1
exact command
cd /PyAutoFit && python -m pytest test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_models_with_tuple_steps test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_jobs_with_tuple_steps -v 2>&1
02.530s8bad2cca
commandcd /PyAutoFit && python -m pytest test_autofit/non_linear/grid/test_sensitivity/ -v 2>&1
exact command
cd /PyAutoFit && python -m pytest test_autofit/non_linear/grid/test_sensitivity/ -v 2>&1
12.451s271915dd
commandpython3 << 'PYEOF' with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'r') as f: content = f.read() # The current code to replace old_code = ''' if isinstance(self.step_size, tuple): half_step = tuple( self.limit_scale * step / 2 for step in self.step_size ) else: half_step = self.limit_scale * self.step_size / 2 for list_ in self._lists: limits = [ ( prior.value_for(max(0.0, centre - half_step[i])), prior.value_for(min(1.0, centre + half_step[i])), ) for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id)) ] yield self.perturb_model.with_limits(limits)''' # New code that handles both cases new_code = ''' if isinstance(self.step_size, tuple): half_step = tuple( self.limit_scale * step / 2 for step in self.step_size ) else: half_step = self.limit_scale * self.step_size / 2 for list_ in self._lists: if isinstance(half_step, tuple): limits = [ ( prior.value_for(max(0.0, centre - half_step[i])), prior.value_for(min(1.0, centre + half_step[i])), ) for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id)) ] else: limits = [ ( prior.value_for(max(0.0, centre - half_step)), prior.value_for(min(1.0, centre + half_step)), ) for centre, prior in zip(list_, self.perturb_model.priors_ordered_by_id) ] yield self.perturb_model.with_limits(limits)''' if old_code in content: new_content = content.replace(old_code, new_code) with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'w') as f: f.write(new_content) print("Successfully replaced the code") else: print("Could not find the old code pattern") PYEOF
exact command
python3 << 'PYEOF'
with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'r') as f:
    content = f.read()

# The current code to replace
old_code = '''        if isinstance(self.step_size, tuple):
            half_step = tuple(
                self.limit_scale * step / 2 for step in self.step_size
            )
        else:
            half_step = self.limit_scale * self.step_size / 2
        for list_ in self._lists:
            limits = [
                (
                    prior.value_for(max(0.0, centre - half_step[i])),
                    prior.value_for(min(1.0, centre + half_step[i])),
                )
                for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id))
            ]
            yield self.perturb_model.with_limits(limits)'''

# New code that handles both cases
new_code = '''        if isinstance(self.step_size, tuple):
            half_step = tuple(
                self.limit_scale * step / 2 for step in self.step_size
            )
        else:
            half_step = self.limit_scale * self.step_size / 2
        for list_ in self._lists:
            if isinstance(half_step, tuple):
                limits = [
                    (
                        prior.value_for(max(0.0, centre - half_step[i])),
                        prior.value_for(min(1.0, centre + half_step[i])),
                    )
                    for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id))
                ]
            else:
                limits = [
                    (
                        prior.value_for(max(0.0, centre - half_step)),
                        prior.value_for(min(1.0, centre + half_step)),
                    )
                    for centre, prior in zip(list_, self.perturb_model.priors_ordered_by_id)
                ]
            yield self.perturb_model.with_limits(limits)'''

if old_code in content:
    new_content = content.replace(old_code, new_code)
    with open('/PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py', 'w') as f:
        f.write(new_content)
    print("Successfully replaced the code")
else:
    print("Could not find the old code pattern")
PYEOF
00.023s0a228f7e
commandsed -n '244,280p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
exact command
sed -n '244,280p' /PyAutoFit/autofit/non_linear/grid/sensitivity/__init__.py
00.003s82f40e6b
KindCommandExitDurationDigest
commandcd /PyAutoFit && python -m pytest test_autofit/non_linear/grid/test_sensitivity/ -v 2>&1
exact command
cd /PyAutoFit && python -m pytest test_autofit/non_linear/grid/test_sensitivity/ -v 2>&1
02.761s68371ed9
commandcd /PyAutoFit && python -m pytest test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_models_with_tuple_steps test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_jobs_with_tuple_steps -v 2>&1
exact command
cd /PyAutoFit && python -m pytest test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_models_with_tuple_steps test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_jobs_with_tuple_steps -v 2>&1
02.278s9798fd96
commandcd /PyAutoFit && git diff --stat
exact command
cd /PyAutoFit && git diff --stat
00.011s88511e78
diffcapture patch
exact command
compute patch in executor staging agent1-8ba1646cd7134af4a49867a107dea542: /opt/homebrew/Cellar/git/2.53.0/bin/git -c core.hooksPath=/dev/null -c core.attributesFile=/dev/null -c diff.external= -c core.quotePath=false add -A && /opt/homebrew/Cellar/git/2.53.0/bin/git -c core.hooksPath=/dev/null -c core.attributesFile=/dev/null -c diff.external= -c core.quotePath=false diff --cached --binary --no-ext-diff --no-textconv --no-renames d564f29a393b75747f1ec3da7c51070d2e12cda1
00.168s1459c61ea27c
Finish summary
Fixed the bug where sensitivity mapping no longer supports tuple step_size. The issue was in the `_perturb_models` property of the `Sensitivity` class which failed with `TypeError: unsupported operand type(s) for /: 'tuple' and 'int'` when `number_of_steps` was specified as a tuple (e.g., `(4, 3, 2)`). The fix modifies the `_perturb_models` property to handle both cases: 1. When `step_size` is a tuple: computes `half_step` as a tuple with one value per dimension 2. When `step_size` is a float: uses the original behavior with a single `half_step` value All 18 sensitivity tests pass, including the two target tests `test_models_with_tuple_steps` and `test_jobs_with_tuple_steps`.
Worker outcome
finished
  • Target tests pass: yes
  • Full suite passes: yes
  • Files changed: autofit/non_linear/grid/sensitivity/__init__.py
  • Ran tests: yes

Witness branch w1

KindCommandExitDurationDigest
applyapply patch 8cb6aa53dc45
exact command
git update-index -q --refresh; git apply -v --3way --recount --ignore-space-change --whitespace=nowarn /opt/receipts-63dec0c50b47409cb0a4d48c63d9df94/incoming/8cb6aa53dc452db2b185d041f98790c0570a38ff8a1cdff674a82e2209791a25.diff
00.013s423eb456501f
verificationpytest (witness) test_autofit/non_linear/grid/test_sensitivity/test_functionality.py
exact command
/opt/receipts-f96a81a4ec4749a2857e93ecbb04c8ed/bin/busybox mkdir -p /opt/receipts/reports && python -I /opt/receipts-f96a81a4ec4749a2857e93ecbb04c8ed/receipts_pytest.py --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning test_autofit/non_linear/grid/test_sensitivity/test_functionality.py -o junit_family=xunit1 -p no:cacheprovider -rA --continue-on-collection-errors --junitxml=/opt/receipts/reports/w1-2.xml
04.453s423eb456501f

Gate predicate checklist

PredicateNameStatusEvidence
R1_fail_to_passFailing specification tests now passPASStarget tests passed: test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_models_with_tuple_steps, test_autofit/non_linear/grid/test_sensitivity/test_functionality.py::test_jobs_with_tuple_steps
R2_pass_to_passPreviously passing tests keptPASS12 baseline-passing tests still pass
R3_no_shrinkNo removed tests or new skipsPASSall 14 baseline tests collected; skips unchanged (0)
R4_patch_scopePatch scopePASSpatch touches 1 file(s), all in scope
R5_provenanceWitness chain and patch digestPASSagent chain 72a58fe1 -> 88511e78 unbroken over 49 receipt(s); witness chain 72a58fe1 -> a4dcc2c4 unbroken over 2 receipt(s); applied digest matches
R6_claim_auditAgent claim audit (advisory)FAILclaimed green but never ran the tests on this branch

What it said

Finish summary
Fixed the bug where sensitivity mapping no longer supports tuple step_size. The issue was in the `_perturb_models` property of the `Sensitivity` class which failed with `TypeError: unsupported operand type(s) for /: 'tuple' and 'int'` when `number_of_steps` was specified as a tuple (e.g., `(4, 3, 2)`). The fix modifies the `_perturb_models` property to handle both cases: 1. When `step_size` is a tuple: computes `half_step` as a tuple with one value per dimension 2. When `step_size` is a float: uses the original behavior with a single `half_step` value All 18 sensitivity tests pass, including the two target tests `test_models_with_tuple_steps` and `test_jobs_with_tuple_steps`.
Worker outcome
finished
  • Target tests pass: yes
  • Full suite passes: yes
  • Files changed: autofit/non_linear/grid/sensitivity/__init__.py
  • Ran tests: yes

What the record shows

unsupported: claimed green but never ran the tests on this branch

SWE-rebench's official grader: resolved

Agrees with the gate

Shipped patch

diff --git a/autofit/non_linear/grid/sensitivity/__init__.py b/autofit/non_linear/grid/sensitivity/__init__.pyindex fa19768c..01d20592 100644--- a/autofit/non_linear/grid/sensitivity/__init__.py+++ b/autofit/non_linear/grid/sensitivity/__init__.py@@ -1,301 +1,315 @@-import csv-import logging-import os-from copy import copy-from pathlib import Path-from typing import List, Generator, Callable, ClassVar, Optional, Union, Tuple--from autoconf.dictable import to_dict-from autofit.mapper.model import ModelInstance-from autofit.mapper.prior_model.abstract import AbstractPriorModel-from autofit.non_linear.grid.grid_search import make_lists, Sequential-from autofit.non_linear.grid.sensitivity.job import Job-from autofit.non_linear.grid.sensitivity.job import JobResult-from autofit.non_linear.grid.sensitivity.result import SensitivityResult-from autofit.non_linear.parallel import Process-from autofit.text.text_util import padding--class Sensitivity:-    def __init__(-        self,-        base_model: AbstractPriorModel,-        perturb_model: AbstractPriorModel,-        simulation_instance,-        paths,-        simulate_cls: Callable,-        base_fit_cls: Callable,-        perturb_fit_cls: Callable,-        job_cls: ClassVar = Job,-        perturb_model_prior_func : Optional[Callable] = None,-        number_of_steps: Union[Tuple[int, ...], int] = 4,-        number_of_cores: int = 2,-        limit_scale: int = 1,-    ):-        """-        Perform sensitivity mapping to evaluate whether a perturbation-        can be detected if it occurs in different parts of an image.--        For a range from 0 to 1 with step_size, for each dimension of the-        perturb_model, a perturbation is created and used in conjunction-        with the instance to create an image.--        For each of these images, a fit is run with just the model and with both-        the model and perturb_model to compare how much better the image-        can be fit if the perturbation is included.--        Parameters-        -----------        base_model-            A model that fits the instance well-        perturb_model-            A model which provides a perturbations to be applied to the instance-            before creating images-        simulation_instance-            An instance of a model to which perturbations are applied prior to-            images being generated-        simulate_cls-            A class which simulates images from each perturb instance that sensitivity mapping is performed on.-        base_fit_cls-            The class which fits the base model to each simulated dataset of the sensitivity map.-        perturb_fit_cls-            The class which fits the perturb model to each simulated dataset of the sensitivity map.-        number_of_steps-            The number of steps for each dimension of the sensitivity grid. If input as a float the dimensions are-            all that value. If input as a tuple of length the number of dimensions, each tuple value is the number of-            steps in that dimension.-        number_of_cores-            How many cores does this computer have?-        limit_scale-            Scales the priors for each perturbation model.-                A scale of 1 means priors have limits the same size as the grid square.-                A scale of 2 means priors have limits larger than the grid square with-                    width twice a grid square.-                A scale of 0.5 means priors have limits smaller than the grid square-                    with width half a grid square.-        """-        self.logger = logging.getLogger(f"Sensitivity ({paths.name})")--        self.logger.info("Creating")--        self.instance = simulation_instance-        self.model = base_model-        self.perturb_model = perturb_model--        self.paths = paths--        self.simulate_cls = simulate_cls-        self.base_fit_cls = base_fit_cls-        self.perturb_fit_cls = perturb_fit_cls--        self.perturb_model_prior_func = perturb_model_prior_func--        self.job_cls = job_cls--        self.number_of_steps = number_of_steps-        self.number_of_cores = number_of_cores--        self.limit_scale = limit_scale--    def run(self) -> SensitivityResult:-        """-        Run fits and comparisons for all perturbations, returning-        a list of results.-        """-        self.logger.info("Running")--        self.paths.save_unique_tag(is_grid_search=True)--        headers = [-            "index",-            *self._headers,-            "log_evidence_increase",-            "log_likelihood_increase",-        ]-        physical_values = list(self._physical_values)--        process_class = Process if self.number_of_cores > 1 else Sequential--        results = list()-        for result in process_class.run_jobs(-            self._make_jobs(), number_of_cores=self.number_of_cores-        ):-            if isinstance(result, Exception):-                raise result--            results.append(result)-            results = sorted(results)--            os.makedirs(self.paths.output_path, exist_ok=True)-            with open(self.results_path, "w+") as f:-                writer = csv.writer(f)-                writer.writerow(headers)-                for result_ in results:--                    values = physical_values[result_.number]-                    writer.writerow(-                        padding(item)-                        for item in [-                            result_.number,-                            *values,-                            result_.log_evidence_increase,-                            result_.log_likelihood_increase,-                        ]-                    )--        result = SensitivityResult(-            samples=[result.result.samples.summary() for result in results],-            perturb_samples=[result.perturb_result.samples.summary() for result in results],-            shape=self.shape-        )--        self.paths.save_json("result", to_dict(result))--        return result--    @property-    def shape(self) -> Tuple[int, ...]:-        """-        Returns the shape of the sensitivity grid.--        The shape is the number of steps performed for each dimension of the `perturb_model`. If sensitivity mapping-        is performed in 3D, the shape will therefore be a tuple of length 3.--        The `shape` can vary across dimensions if the `number_of_steps` parameter is input as a tuple.--        Returns-        --------        The shape of the sensitivity grid.-        """--        if isinstance(self.number_of_steps, tuple):-            return self.number_of_steps--        return tuple(self.number_of_steps for _ in range(self.perturb_model.prior_count))--    @property-    def step_size(self) -> Union[float, Tuple]:-        """-        Returns-        --------        step_size-            The size of a step in any given dimension in hyper space.-        """-        if isinstance(self.number_of_steps, tuple):-            return tuple(1 / number_of_steps for number_of_steps in self.number_of_steps)-        return 1 / self.number_of_steps--    @property-    def results_path(self) -> Path:-        return self.paths.output_path / "results.csv"--    @property-    def _lists(self) -> List[List[float]]:-        """-        A list of hypercube vectors, used to instantiate-        the perturb_model and create the individual-        perturbations.-        """-        return make_lists(self.perturb_model.prior_count, step_size=self.step_size)--    @property-    def _physical_values(self) -> List[List[float]]:-        """-        Lists of physical values for each grid square-        """-        return [-            [-                prior.value_for(unit_value)-                for prior, unit_value in zip(-                    self.perturb_model.priors_ordered_by_id, unit_values-                )-            ]-            for unit_values in self._lists-        ]--    @property-    def _headers(self) -> Generator[str, None, None]:-        """-        A name for each of the perturb priors-        """-        for path, _ in self.perturb_model.prior_tuples:-            yield path--    @property-    def _labels(self) -> Generator[str, None, None]:-        """-        One label for each perturbation, used to distinguish-        fits for each perturbation by placing them in separate-        directories.-        """-        for list_ in self._lists:-            strings = list()-            for value, prior_tuple in zip(list_, self.perturb_model.prior_tuples):-                path, prior = prior_tuple-                value = prior.value_for(value)-                strings.append(f"{path}_{value}")-            yield "_".join(strings)--    @property-    def _perturb_instances(self) -> Generator[ModelInstance, None, None]:-        """-        A list of instances each of which defines a perturbation to-        be applied to the image.-        """-        for list_ in self._lists:-            yield self.perturb_model.instance_from_unit_vector(list_)--    @property-    def _perturb_models(self) -> Generator[AbstractPriorModel, None, None]:-        """-        A list of models representing a perturbation at each grid square.--        By default models have priors with limits at the edges of a grid square.-        These limits can be scaled using the limit_scale variable. If the variable-        is 2 then the priors will have width twice the step size.-        """-        half_step = self.limit_scale * self.step_size / 2-        for list_ in self._lists:-            limits = [-                (-                    prior.value_for(max(0.0, centre - half_step)),-                    prior.value_for(min(1.0, centre + half_step)),-                )-                for centre, prior in zip(list_, self.perturb_model.priors_ordered_by_id)-            ]-            yield self.perturb_model.with_limits(limits)--    def _make_jobs(self) -> Generator[Job, None, None]:-        """-        Create a list of jobs to be run on separate processes.--        Each job fits a perturb image with the original model-        and a model which includes a perturbation.-        """-        for number, (perturb_instance, perturb_model, label) in enumerate(-            zip(self._perturb_instances, self._perturb_models, self._labels)-        ):--            if self.perturb_model_prior_func is not None:-                perturb_model = self.perturb_model_prior_func(-                    perturb_instance=perturb_instance,-                    perturb_model=perturb_model-                )--            simulate_instance = copy(self.instance)-            simulate_instance.perturb = perturb_instance--            paths = self.paths.for_sub_analysis(-                label,-            )--            yield self.job_cls(-                simulate_instance=simulate_instance,-                model=self.model,-                perturb_model=perturb_model,-                base_instance=self.instance,-                simulate_cls=self.simulate_cls,-                base_fit_cls=self.base_fit_cls,-                perturb_fit_cls=self.perturb_fit_cls,-                paths=paths,-                number=number,+import csv+import logging+import os+from copy import copy+from pathlib import Path+from typing import List, Generator, Callable, ClassVar, Optional, Union, Tuple++from autoconf.dictable import to_dict+from autofit.mapper.model import ModelInstance+from autofit.mapper.prior_model.abstract import AbstractPriorModel+from autofit.non_linear.grid.grid_search import make_lists, Sequential+from autofit.non_linear.grid.sensitivity.job import Job+from autofit.non_linear.grid.sensitivity.job import JobResult+from autofit.non_linear.grid.sensitivity.result import SensitivityResult+from autofit.non_linear.parallel import Process+from autofit.text.text_util import padding++class Sensitivity:+    def __init__(+        self,+        base_model: AbstractPriorModel,+        perturb_model: AbstractPriorModel,+        simulation_instance,+        paths,+        simulate_cls: Callable,+        base_fit_cls: Callable,+        perturb_fit_cls: Callable,+        job_cls: ClassVar = Job,+        perturb_model_prior_func : Optional[Callable] = None,+        number_of_steps: Union[Tuple[int, ...], int] = 4,+        number_of_cores: int = 2,+        limit_scale: int = 1,+    ):+        """+        Perform sensitivity mapping to evaluate whether a perturbation+        can be detected if it occurs in different parts of an image.++        For a range from 0 to 1 with step_size, for each dimension of the+        perturb_model, a perturbation is created and used in conjunction+        with the instance to create an image.++        For each of these images, a fit is run with just the model and with both+        the model and perturb_model to compare how much better the image+        can be fit if the perturbation is included.++        Parameters+        ----------+        base_model+            A model that fits the instance well+        perturb_model+            A model which provides a perturbations to be applied to the instance+            before creating images+        simulation_instance+            An instance of a model to which perturbations are applied prior to+            images being generated+        simulate_cls+            A class which simulates images from each perturb instance that sensitivity mapping is performed on.+        base_fit_cls+            The class which fits the base model to each simulated dataset of the sensitivity map.+        perturb_fit_cls+            The class which fits the perturb model to each simulated dataset of the sensitivity map.+        number_of_steps+            The number of steps for each dimension of the sensitivity grid. If input as a float the dimensions are+            all that value. If input as a tuple of length the number of dimensions, each tuple value is the number of+            steps in that dimension.+        number_of_cores+            How many cores does this computer have?+        limit_scale+            Scales the priors for each perturbation model.+                A scale of 1 means priors have limits the same size as the grid square.+                A scale of 2 means priors have limits larger than the grid square with+                    width twice a grid square.+                A scale of 0.5 means priors have limits smaller than the grid square+                    with width half a grid square.+        """+        self.logger = logging.getLogger(f"Sensitivity ({paths.name})")++        self.logger.info("Creating")++        self.instance = simulation_instance+        self.model = base_model+        self.perturb_model = perturb_model++        self.paths = paths++        self.simulate_cls = simulate_cls+        self.base_fit_cls = base_fit_cls+        self.perturb_fit_cls = perturb_fit_cls++        self.perturb_model_prior_func = perturb_model_prior_func++        self.job_cls = job_cls++        self.number_of_steps = number_of_steps+        self.number_of_cores = number_of_cores++        self.limit_scale = limit_scale++    def run(self) -> SensitivityResult:+        """+        Run fits and comparisons for all perturbations, returning+        a list of results.+        """+        self.logger.info("Running")++        self.paths.save_unique_tag(is_grid_search=True)++        headers = [+            "index",+            *self._headers,+            "log_evidence_increase",+            "log_likelihood_increase",+        ]+        physical_values = list(self._physical_values)++        process_class = Process if self.number_of_cores > 1 else Sequential++        results = list()+        for result in process_class.run_jobs(+            self._make_jobs(), number_of_cores=self.number_of_cores+        ):+            if isinstance(result, Exception):+                raise result++            results.append(result)+            results = sorted(results)++            os.makedirs(self.paths.output_path, exist_ok=True)+            with open(self.results_path, "w+") as f:+                writer = csv.writer(f)+                writer.writerow(headers)+                for result_ in results:++                    values = physical_values[result_.number]+                    writer.writerow(+                        padding(item)+                        for item in [+                            result_.number,+                            *values,+                            result_.log_evidence_increase,+                            result_.log_likelihood_increase,+                        ]+                    )++        result = SensitivityResult(+            samples=[result.result.samples.summary() for result in results],+            perturb_samples=[result.perturb_result.samples.summary() for result in results],+            shape=self.shape+        )++        self.paths.save_json("result", to_dict(result))++        return result++    @property+    def shape(self) -> Tuple[int, ...]:+        """+        Returns the shape of the sensitivity grid.++        The shape is the number of steps performed for each dimension of the `perturb_model`. If sensitivity mapping+        is performed in 3D, the shape will therefore be a tuple of length 3.++        The `shape` can vary across dimensions if the `number_of_steps` parameter is input as a tuple.++        Returns+        -------+        The shape of the sensitivity grid.+        """++        if isinstance(self.number_of_steps, tuple):+            return self.number_of_steps++        return tuple(self.number_of_steps for _ in range(self.perturb_model.prior_count))++    @property+    def step_size(self) -> Union[float, Tuple]:+        """+        Returns+        -------+        step_size+            The size of a step in any given dimension in hyper space.+        """+        if isinstance(self.number_of_steps, tuple):+            return tuple(1 / number_of_steps for number_of_steps in self.number_of_steps)+        return 1 / self.number_of_steps++    @property+    def results_path(self) -> Path:+        return self.paths.output_path / "results.csv"++    @property+    def _lists(self) -> List[List[float]]:+        """+        A list of hypercube vectors, used to instantiate+        the perturb_model and create the individual+        perturbations.+        """+        return make_lists(self.perturb_model.prior_count, step_size=self.step_size)++    @property+    def _physical_values(self) -> List[List[float]]:+        """+        Lists of physical values for each grid square+        """+        return [+            [+                prior.value_for(unit_value)+                for prior, unit_value in zip(+                    self.perturb_model.priors_ordered_by_id, unit_values+                )+            ]+            for unit_values in self._lists+        ]++    @property+    def _headers(self) -> Generator[str, None, None]:+        """+        A name for each of the perturb priors+        """+        for path, _ in self.perturb_model.prior_tuples:+            yield path++    @property+    def _labels(self) -> Generator[str, None, None]:+        """+        One label for each perturbation, used to distinguish+        fits for each perturbation by placing them in separate+        directories.+        """+        for list_ in self._lists:+            strings = list()+            for value, prior_tuple in zip(list_, self.perturb_model.prior_tuples):+                path, prior = prior_tuple+                value = prior.value_for(value)+                strings.append(f"{path}_{value}")+            yield "_".join(strings)++    @property+    def _perturb_instances(self) -> Generator[ModelInstance, None, None]:+        """+        A list of instances each of which defines a perturbation to+        be applied to the image.+        """+        for list_ in self._lists:+            yield self.perturb_model.instance_from_unit_vector(list_)++    @property+    def _perturb_models(self) -> Generator[AbstractPriorModel, None, None]:+        """+        A list of models representing a perturbation at each grid square.++        By default models have priors with limits at the edges of a grid square.+        These limits can be scaled using the limit_scale variable. If the variable+        is 2 then the priors will have width twice the step size.+        """+        if isinstance(self.step_size, tuple):+            half_step = tuple(+                self.limit_scale * step / 2 for step in self.step_size+            )+        else:+            half_step = self.limit_scale * self.step_size / 2+        for list_ in self._lists:+            if isinstance(half_step, tuple):+                limits = [+                    (+                        prior.value_for(max(0.0, centre - half_step[i])),+                        prior.value_for(min(1.0, centre + half_step[i])),+                    )+                    for i, (centre, prior) in enumerate(zip(list_, self.perturb_model.priors_ordered_by_id))+                ]+            else:+                limits = [+                    (+                        prior.value_for(max(0.0, centre - half_step)),+                        prior.value_for(min(1.0, centre + half_step)),+                    )+                    for centre, prior in zip(list_, self.perturb_model.priors_ordered_by_id)+                ]+            yield self.perturb_model.with_limits(limits)++    def _make_jobs(self) -> Generator[Job, None, None]:+        """+        Create a list of jobs to be run on separate processes.++        Each job fits a perturb image with the original model+        and a model which includes a perturbation.+        """+        for number, (perturb_instance, perturb_model, label) in enumerate(+            zip(self._perturb_instances, self._perturb_models, self._labels)+        ):++            if self.perturb_model_prior_func is not None:+                perturb_model = self.perturb_model_prior_func(+                    perturb_instance=perturb_instance,+                    perturb_model=perturb_model+                )++            simulate_instance = copy(self.instance)+            simulate_instance.perturb = perturb_instance++            paths = self.paths.for_sub_analysis(+                label,+            )++            yield self.job_cls(+                simulate_instance=simulate_instance,+                model=self.model,+                perturb_model=perturb_model,+                base_instance=self.instance,+                simulate_cls=self.simulate_cls,+                base_fit_cls=self.base_fit_cls,+                perturb_fit_cls=self.perturb_fit_cls,+                paths=paths,+                number=number,             )\ No newline at end of file

Model and costs

Model
nvidia/Nemotron-3_5-Lightning thinking off · temperature 1.0 · top_p 0.95
Tokens
565,974 tokens
Model cost
$0.04
Sandbox time
2 min 36 s
Sandbox cost
Token Factory reported cost: 0.280 (unit not published)
Full environment disclosure
{
  "executor_kind": "tokenfactory",
  "lock_sha256": "7eee3248ca81b3098f6a555415bbc33c53453d2a8c2136edb953ff27a7ab3f4e",
  "model": {
    "base_url": "https://api.tokenfactory.nebius.com/v1/",
    "client": "TokenFactoryClient",
    "max_tokens": null,
    "model": "nvidia/Nemotron-3_5-Lightning",
    "temperature": 1.0,
    "thinking": false,
    "top_p": 0.95
  },
  "pytest_config_digests": {
    "setup.cfg": "9b66264f6dfdb9b8d5e467a673e97ae8923126df6845976f1317ca89fd45ff3d",
    "test_autofit/aggregator/conftest.py": "ba0806ea5d339eab14b86d0e7875e21edc33d17b057f2de2b6f46aa2e9b88081",
    "test_autofit/analysis/conftest.py": "95712a522b3a8ce017bbb13fffe6937c788314cb51159fcc3e4acbc11a5d9708",
    "test_autofit/conftest.py": "a6a3c2e6f4abad383a10259bfe95a3ebc56f27e0c5357f4c5cf59a8501a26491",
    "test_autofit/database/conftest.py": "e2873b4d62eda9a8703735913c5ca7254ea42544ef38111399b40205b49046db",
    "test_autofit/database/migration/conftest.py": "fc9a54c6c798411f35e6d8016b5164a4006521a17939f30e9007d3353323fb54",
    "test_autofit/database/paths/conftest.py": "7f4875d5bc29d5f3e9ecfbfb0901988c499b9773b0cf05254752d824703a5303",
    "test_autofit/database/query/internal/conftest.py": "2d3fa1ba12240e395ea4388c77f7cce09aa6bf4a365ece914419ada5f59895a2",
    "test_autofit/database/query/query/conftest.py": "1288798fe779fc8a1146d721e30a1ce285459cdc4215f8b1be0bb40e72700e15",
    "test_autofit/graphical/conftest.py": "c849c96fd580a1389dd7949470b45e94ab0182b4cdac42230545dd7e109e734b",
    "test_autofit/graphical/gaussian/conftest.py": "1fdf23173ad6888bd93e78442c0eb19d25a0eabe7d7ba26eccdbafa2a316a0fa",
    "test_autofit/graphical/global/conftest.py": "2750a96c2744ee9c36aecaff4b725f32d988abf07435a3be1b0c8d447b698f5e",
    "test_autofit/graphical/hierarchical/conftest.py": "66960dc7d6b2a4d59c6d4214bd9d13ea6c553969cb65330c8cca089bc694e544",
    "test_autofit/graphical/info/conftest.py": "2affc805c5024beebd2c6987bfe6f7c16f294e467f669c270fb34acd07ca705c",
    "test_autofit/graphical/regression/conftest.py": "1eb70568b1b75cb4821a6f2a2fb0dcb176affbeeccc85496486e25a72a49c216",
    "test_autofit/interpolator/conftest.py": "31aec003629b7141e56e2a85a86a8e79971e0fd49eeab751710e48eed00ef375",
    "test_autofit/mapper/conftest.py": "b3c4eb4206df9b624b456d04ed01c3efbaa2040d0283d630e7c2ef6cbdf1125e",
    "test_autofit/mapper/model/serialise/conftest.py": "29fc7cf4c507bf4fb1142e73a8ad30adceb258f39c977d685798d42ff78f8e24",
    "test_autofit/mapper/with_paths/conftest.py": "6b8355134c4d86a4eb6e18cc4aa08076c8c5b38f19406ecd6f7ed3ebf8452ab0",
    "test_autofit/non_linear/grid/conftest.py": "a58386eed88b836f1c5f94fe67c728e29f508fd2b991fd6d3dcd174ae2d129d2",
    "test_autofit/non_linear/grid/test_paths/conftest.py": "09d34f250e131390f01086d8481cf2bcccac3a5943c1093f145eb7621ce99210",
    "test_autofit/non_linear/grid/test_sensitivity/conftest.py": "61fe1b94ec19a728a05d8e07e082ccad1da56f5568489bcf3038501e207d4c8f",
    "test_autofit/serialise/conftest.py": "c690179a6be4fadc1d1c329050234c657e40c5a80c43778e1157c1095b6173a4"
  },
  "receipts_version": "0.0.1",
  "test_digests": {
    "test_autofit/non_linear/grid/test_sensitivity/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "test_autofit/non_linear/grid/test_sensitivity/conftest.py": "61fe1b94ec19a728a05d8e07e082ccad1da56f5568489bcf3038501e207d4c8f",
    "test_autofit/non_linear/grid/test_sensitivity/test_functionality.py": "b5d035262907de4fc40e43f16e5136ec7f897602d4725fdbd0bcf4fedcf58b7b",
    "test_autofit/non_linear/grid/test_sensitivity/test_results.py": "8bb90a64b8c259f680b4516d2a964ba90d8f3eb5194fc257f695f80329081118",
    "test_autofit/non_linear/grid/test_sensitivity/test_run.py": "0828e32a026962aeb8c78c2a3d345256efb3217abca9c24016495ca1ee363901"
  }
}

The bundle

Recorded JSON and patches. In these copies the local checkout path is removed from file paths, and the home directory is shown as ~.