From 97852729e33e4f8640496f8da0ef98df62352113 Mon Sep 17 00:00:00 2001 From: lewisychen Date: Fri, 4 Sep 2026 22:49:42 -0600 Subject: [PATCH 1/2] A subprogram whose declined draws outnumber its trials fails by name; a dummy handed to what might write it is not read-only Three rows of #32 (12, 13, 14), all on main since #30 merged. * The differential gate declines a draw the candidate stops or overruns on (a translated ERROR STOP, a subscript past a dummy's extent) and one both sides take to NaN, and draws again. It did so up to 24 times per trial, counted the redraws in a per-subprogram metric nobody repeated, and passed once one draw survived -- so a translation that stopped on inputs the source accepts passed on the survivors, one at a time. Now a subprogram whose declined draws outnumber the trials it was compared on fails by name, with the count, the reason of each decline (error stop, subscript past extent, NaN on both sides) and the remedy: narrow the draw with `ranges` or pin `dims`. The reshaped rule, which names the extent, still speaks first. A passing verdict repeats the declined count and reasons in its detail; the per-subprogram metrics carry `declined` by reason beside `redrawn`. * Read-only intent inference took a parenthesised reference whose base is not one of this file's subprograms for a subscript, and never walked a function reference, an internal WRITE or an ASSOCIATE. A dummy modified through a use-associated function -- which parses exactly like a subscript -- was inferred `intent(in)`, dropped from the wrapper's outputs and compared on neither side. The pass now knows the scope's variables (dummies, locals, local parameters, module state); a base it does not name is a call whose variable actuals escape, an expression actual is a temporary and does not, and the WRITE unit and the ASSOCIATE selector escape. What cannot be proved read-only stays UNKNOWN, and the gate keeps refusing the routine by name, as it did before #30. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HYzzevwvTuLBMYjzdHUhGg --- src/recast/fortran/interface.py | 96 ++++++++++++++++++++++++++++----- src/recast/verify/bitexact.py | 55 +++++++++++++++++-- tests/test_bitexact_draws.py | 61 +++++++++++++++++---- tests/test_fortran_analysis.py | 37 +++++++++++++ 4 files changed, 224 insertions(+), 25 deletions(-) diff --git a/src/recast/fortran/interface.py b/src/recast/fortran/interface.py index 98b9234..6716078 100644 --- a/src/recast/fortran/interface.py +++ b/src/recast/fortran/interface.py @@ -1226,7 +1226,7 @@ def is_public(name: str) -> bool: for s in subs ] _infer_write_only_intents(subs, subprograms) - _infer_read_only_intents(subs, subprograms, sub_names) + _infer_read_only_intents(subs, subprograms, sub_names, set(state_names)) # After the inference, not before: an intent this pass just gave a # dummy is one this rule has to see. _mark_buffer_out_arrays(subprograms, every=buffer_out_arrays == "all") @@ -1449,15 +1449,22 @@ def _mark_buffer_out_arrays(records: list[dict[str, Any]], every: bool = False) argument["buffer"] = True -def _written_or_escaping(exec_part: Any, sub_names: set[str]) -> set[str]: +def _written_or_escaping( + exec_part: Any, sub_names: set[str], variables: set[str] | None = None +) -> set[str]: """Names this execution part could change, read conservatively. A name is here if it is assigned to, if it controls a DO, if a READ fills - it, if an ALLOCATE/DEALLOCATE/NULLIFY names it -- or if it is handed to - something that might write it. An intrinsic never writes its argument and - a subscript is not a call, so neither of those escapes; a reference to one - of this file's own subprograms, and anything fparser could not resolve to - either, does. + it or a WRITE takes it as the internal unit, if an ALLOCATE, DEALLOCATE, + NULLIFY or INQUIRE names it, if an ASSOCIATE takes it as a selector -- or + if it is handed to something that might write it: a CALL, a function + reference, or a parenthesised reference whose base is not a variable this + scope declares. An intrinsic never writes its argument and a subscript of + a declared array is not a call, so neither of those escapes. A reference + to a use-associated or external procedure parses exactly like a subscript, + so a base that ``variables`` does not name is taken for a call and its + variable actuals escape; with no ``variables`` given, only this file's + own subprograms are calls, as before. """ escaping: set[str] = set() @@ -1469,6 +1476,25 @@ def leftmost(node: Any) -> str | None: node = children[0] return str(node).lower() if isinstance(node, f03.Name) else None + def actuals(arguments: Any) -> list[Any]: + if arguments is None: + return [] + if type(arguments).__name__.endswith("_List"): + return list(arguments.children) + return [arguments] + + def variable_actual(item: Any) -> str | None: + """The variable an actual argument names, if the callee could write + it: a bare name, a keyword form of one, an element or component of + one. An expression is a temporary and nobody's to write.""" + if isinstance(item, f03.Actual_Arg_Spec): + return variable_actual(item.children[1]) + if isinstance(item, f03.Name): + return str(item).lower() + if isinstance(item, (f03.Part_Ref, f03.Data_Ref)): + return leftmost(item) + return None + for assignment in walk(exec_part, (f03.Assignment_Stmt, f03.Pointer_Assignment_Stmt)): name = leftmost(assignment.children[0]) if name: @@ -1479,27 +1505,62 @@ def leftmost(node: Any) -> str | None: break for statement in walk( exec_part, - (f03.Read_Stmt, f03.Allocate_Stmt, f03.Deallocate_Stmt, f03.Nullify_Stmt), + ( + f03.Read_Stmt, + f03.Allocate_Stmt, + f03.Deallocate_Stmt, + f03.Nullify_Stmt, + f03.Inquire_Stmt, + ), ): for name in walk(statement, f03.Name): escaping.add(str(name).lower()) + for statement in walk(exec_part, f03.Write_Stmt): + # ``write(buf, fmt) ...``: a character variable as the unit is the + # thing written. + for spec in walk(statement.children[0], f03.Io_Control_Spec): + key, value = spec.children + if key in (None, "UNIT") and isinstance(value, f03.Name): + escaping.add(str(value).lower()) + for association in walk(exec_part, f03.Association): + name = leftmost(association.children[2]) + if name: + escaping.add(name) for call in walk(exec_part, f03.Call_Stmt): arguments = call.children[1] for name in walk(arguments, f03.Name) if arguments is not None else []: escaping.add(str(name).lower()) + for function in walk(exec_part, f03.Function_Reference): + for item in actuals(function.children[1]): + name = variable_actual(item) + if name: + escaping.add(name) for reference in walk(exec_part, (f03.Part_Ref, f03.Structure_Constructor)): base = reference.children[0] if not isinstance(base, f03.Name): continue - if isinstance(reference, f03.Part_Ref) and str(base).lower() not in sub_names: - continue # a subscript, or a reference to something with no body here + base_name = str(base).lower() + if isinstance(reference, f03.Part_Ref) and base_name not in sub_names: + if variables is None or base_name in variables: + continue # a subscript + # Not a variable this scope declares and not a subprogram of this + # file: a use-associated or external procedure, parsed as a + # subscript. Its variable actuals are the callee's to write. + for item in actuals(reference.children[1]): + name = variable_actual(item) + if name: + escaping.add(name) + continue for name in walk(reference.children[1], f03.Name): escaping.add(str(name).lower()) return escaping def _infer_read_only_intents( - subs: list[Any], records: list[dict[str, Any]], sub_names: set[str] + subs: list[Any], + records: list[dict[str, Any]], + sub_names: set[str], + state_names: set[str] | None = None, ) -> None: """Give a dummy the body never changes the intent its use says it has. @@ -1509,6 +1570,11 @@ def _infer_read_only_intents( for anything downstream that has to know whether the value after the call is an output -- the differential gate refuses the whole subprogram over it, so one undeclared argument costs the routine its evidence. + + Read-only has to be proved, not assumed: a dummy handed to a function, to + a procedure this file does not define, to an internal WRITE or an + ASSOCIATE stays UNKNOWN, and the gate keeps refusing the routine by name + rather than comparing it with an output missing on both sides. """ by_name = {sub_name_of(s): s for s in subs} for record in records: @@ -1525,7 +1591,13 @@ def _infer_read_only_intents( exec_part = next((c for c in node.children if isinstance(c, f03.Execution_Part)), None) if exec_part is None: continue - escaping = _written_or_escaping(exec_part, sub_names) + variables = ( + {a["name"] for a in record["args"]} + | {local["name"] for local in record.get("locals") or []} + | {p["name"] for p in record.get("local_parameters") or []} + | set(state_names or ()) + ) + escaping = _written_or_escaping(exec_part, sub_names, variables) for argument in candidates: if argument["name"] not in escaping: argument["intent"] = "IN" diff --git a/src/recast/verify/bitexact.py b/src/recast/verify/bitexact.py index 41e4187..0a5095f 100644 --- a/src/recast/verify/bitexact.py +++ b/src/recast/verify/bitexact.py @@ -47,6 +47,25 @@ DEFAULT_RANGE = (-1000.0, 1000.0) DEFAULT_INTEGER_RANGE = (1, 8) + + +def _declined_summary(declined_by: dict[str, int]) -> str: + """``"3 error stop, 1 NaN on both sides"``: the declined draws by kind.""" + return ( + ", ".join(f"{count} {why}" for why, count in sorted(declined_by.items())) + or "no reason recorded" + ) + + +def _redrawn_note(totals: dict[str, Any]) -> str: + """What a passing verdict says about the draws it did not compare on.""" + redrawn = int(totals.get("redrawn") or 0) + if not redrawn: + return "" + reasons = _declined_summary(totals.get("declined") or {}) + return f"; {redrawn} draw(s) declined and drawn again ({reasons})" + + DEFAULT_DIMENSION = 8 SUPPORTED_DTYPES = frozenset({"float32", "float64", "int32", "int64", "bool"}) PROCEDURE_DTYPE = "PROCEDURE" @@ -244,8 +263,13 @@ class BitexactVerifier(Verifier): Bounded, and the bound is the point: a subprogram whose every draw is refused is reported as one that could not be compared, which is what - ``uncovered`` and the coverage gate above are for. The number of redraws - is recorded per subprogram so the count is never silent. + ``uncovered`` and the coverage gate above are for. And a subprogram whose + declined draws outnumber the trials it was compared on fails by name: + the trials that survived are a minority of what the configured draw + produces, and a translation that stops or overruns on inputs the source + accepts would pass on the handful it did not, one survivor at a time. + The number of redraws, and the reason for each, is recorded per + subprogram and repeated on the verdict, so the count is never silent. Two things a redraw is not for. A NaN on one side only is a mismatch between the sides, not a domain draw, and is counted as one; only a trial @@ -441,13 +465,15 @@ def generable(name: str) -> bool: per_subprogram: dict[str, dict[str, Any]] = {} failures: list[str] = [] worst_rel = 0.0 - totals = { + totals: dict[str, Any] = { "points": 0, "bit_exact": 0, "max_ulp": 0, "nan_mismatch": 0, "integer_points": 0, "integer_mismatch": 0, + "redrawn": 0, + "declined": {}, } for name in wanted: sub = table[name] @@ -485,6 +511,9 @@ def generable(name: str) -> bool: totals["nan_mismatch"] += outcome["nan_mismatch"] totals["integer_points"] += outcome["integer_points"] totals["integer_mismatch"] += outcome["integer_mismatch"] + totals["redrawn"] += outcome.get("redrawn", 0) + for why, count in (outcome.get("declined") or {}).items(): + totals["declined"][why] = totals["declined"].get(why, 0) + count worst_rel = max(worst_rel, outcome["max_rel"]) if "max_ulp_dominant" in outcome: totals["max_ulp_dominant"] = max( @@ -603,7 +632,7 @@ def _award( Confidence.BIT_EXACT, metrics, f"{totals['points']} points across {len(per_subprogram)} " - f"subprogram(s), all bit-exact", + f"subprogram(s), all bit-exact" + _redrawn_note(totals), ) if rtol is not None and worst_rel <= float(rtol): return self._verdict( @@ -738,6 +767,8 @@ def _compare_subprogram( dominant_points = 0 max_rel = 0.0 redrawn = 0 + # Why each declined draw was declined, by kind, so the verdict can say. + declined_by: dict[str, int] = {} # Trials that were compared only after a shape refusal moved the free # extents off the configured ones. reshaped = 0 @@ -901,6 +932,8 @@ def _compare_subprogram( # called on this draw; draw again. declined = f"candidate raised: {type(error).__name__}: {error}" reshape = reshape or isinstance(error, IndexError) + why = "subscript past extent" if isinstance(error, IndexError) else "error stop" + declined_by[why] = declined_by.get(why, 0) + 1 redrawn += 1 continue except Exception as error: @@ -1029,6 +1062,7 @@ def _compare_subprogram( staged.append(measured) if reason: declined = reason + declined_by["NaN on both sides"] = declined_by.get("NaN on both sides", 0) + 1 redrawn += 1 continue if reshape: @@ -1064,6 +1098,18 @@ def _compare_subprogram( f"values; the {points} point(s) that fit are not evidence at those extents. " "Pin `dims` to extents the subprogram takes" } + if samples is None and redrawn > len(rounds): + # More draws were declined than trials compared: the survivors + # are a minority of what the configured draw produces, and a + # candidate that stops or overruns where the source does not + # would pass on exactly that minority. Unverified at the + # configured draw, and says so by name rather than passing on it. + return { + "error": f"{redrawn} draw(s) were declined ({_declined_summary(declined_by)}) " + f"to compare {len(rounds)} trial(s); the trials compared are a minority of " + "what the configured draw produces and are not evidence about the rest. " + "Narrow the draw with `ranges`, or pin `dims`, to values the subprogram takes" + } outcome = { "points": points, "bit_exact": bit_exact, @@ -1073,6 +1119,7 @@ def _compare_subprogram( "integer_points": integer_points, "integer_mismatch": integer_mismatch, "redrawn": redrawn, + "declined": declined_by, "reshaped": reshaped, } if dominant_at is not None: diff --git a/tests/test_bitexact_draws.py b/tests/test_bitexact_draws.py index 5109ebc..00e87c8 100644 --- a/tests/test_bitexact_draws.py +++ b/tests/test_bitexact_draws.py @@ -82,8 +82,9 @@ def probe(mode, x): def test_a_draw_the_source_stops_on_is_drawn_again(tmp_path: Path) -> None: - """``mode`` is sampled from the harness's default integer range and only - two of those values are ones the subprogram takes.""" + """``mode`` is drawn from 1 to 3 and only two of those values are ones the + subprogram takes: the third is declined and drawn again, and the verdict + says how many times.""" def w_probe(mode: Any, x: Any) -> Any: # An ERROR STOP on the reference side ends the process; the harness @@ -92,9 +93,29 @@ def w_probe(mode: Any, x: Any) -> Any: assert int(mode) in (1, 2), "the reference was called on a refused draw" return x * 2.0 - verdict = judge(tmp_path, MODE, SimpleNamespace(w_probe=w_probe)) + verdict = judge(tmp_path, MODE, SimpleNamespace(w_probe=w_probe), ranges={"mode": (1, 3)}) assert verdict.confidence is Confidence.BIT_EXACT, verdict.detail - assert verdict.metrics["subprograms"]["probe"]["redrawn"] > 0 + redrawn = verdict.metrics["subprograms"]["probe"]["redrawn"] + assert redrawn > 0 + assert verdict.metrics["subprograms"]["probe"]["declined"] == {"error stop": redrawn} + assert f"{redrawn} draw(s) declined and drawn again ({redrawn} error stop)" in verdict.detail + + +def test_a_subprogram_whose_draws_are_mostly_declined_fails_by_name(tmp_path: Path) -> None: + """``mode`` from the default integer range, 1 to 8, and the subprogram + takes two of them: three draws in four are declined. The survivors are a + minority of the configured draw, and a candidate that stopped on inputs + the source accepts would pass on that minority one survivor at a time -- + so it fails by name, with the count, the reason and the remedy.""" + verdict = judge(tmp_path, MODE, SimpleNamespace(w_probe=lambda mode, x: x * 2.0)) + assert verdict.confidence is Confidence.FAILED + detail = verdict.detail or "" + assert "probe: " in detail and "draw(s) were declined (" in detail + assert "error stop) to compare 10 trial(s)" in detail + assert "Narrow the draw with `ranges`" in detail + assert verdict.metrics["subprograms"]["probe"] == { + "error": verdict.metrics["subprograms"]["probe"]["error"] + } def test_every_draw_refused_is_a_subprogram_that_could_not_be_compared(tmp_path: Path) -> None: @@ -181,7 +202,7 @@ def test_pinned_extents_the_body_takes_are_not_redrawn(tmp_path: Path) -> None: def probe(x): with np.errstate(invalid="ignore"): - return np.sqrt(x) + return np.sqrt(x + 500.0) """ @@ -193,12 +214,34 @@ def test_a_draw_both_sides_take_to_nan_is_drawn_again(tmp_path: Path) -> None: def w_probe(x: Any) -> Any: with np.errstate(invalid="ignore"): - return np.sqrt(x) + return np.sqrt(x + 500.0) verdict = judge(tmp_path, NAN, SimpleNamespace(w_probe=w_probe)) assert verdict.confidence is Confidence.BIT_EXACT, verdict.detail - assert verdict.metrics["subprograms"]["probe"]["redrawn"] > 0 + redrawn = verdict.metrics["subprograms"]["probe"]["redrawn"] + assert redrawn > 0 + assert verdict.metrics["subprograms"]["probe"]["declined"] == {"NaN on both sides": redrawn} assert verdict.metrics["nan_mismatch"] == 0 + assert "NaN on both sides" in verdict.detail + + +def test_a_subprogram_that_mostly_goes_to_nan_on_both_sides_fails_by_name(tmp_path: Path) -> None: + """Both sides agree on the NaN, on three draws in four. A NaN agreeing with + a NaN is not evidence, and the draws that did compare are a minority of + the configured range: the operator has to narrow the range, and the + verdict says so rather than passing on the quarter that fit.""" + + def w_probe(x: Any) -> Any: + with np.errstate(invalid="ignore"): + return np.sqrt(x - 500.0) + + verdict = judge( + tmp_path, NAN.replace("x + 500.0", "x - 500.0"), SimpleNamespace(w_probe=w_probe) + ) + assert verdict.confidence is Confidence.FAILED + detail = verdict.detail or "" + assert "draw(s) were declined (" in detail and "NaN on both sides) to compare" in detail + assert "Narrow the draw with `ranges`" in detail def test_a_nan_on_one_side_only_is_a_mismatch_not_a_redraw(tmp_path: Path) -> None: @@ -208,7 +251,7 @@ def test_a_nan_on_one_side_only_is_a_mismatch_not_a_redraw(tmp_path: Path) -> No exactly the narrowing the bound exists to prevent.""" def w_probe(x: Any) -> Any: - return np.sqrt(x) if x >= 0.0 else np.float64(0.0) + return np.sqrt(x + 500.0) if x >= -500.0 else np.float64(0.0) verdict = judge(tmp_path, NAN, SimpleNamespace(w_probe=w_probe)) assert verdict.confidence is Confidence.FAILED @@ -220,7 +263,7 @@ def test_a_draw_that_needs_no_redrawing_is_the_one_the_seed_names(tmp_path: Path """The first draw of every trial is unchanged -- same seed, same extents -- so a run that never has to redraw compares exactly what it compared before.""" - plain = NAN.replace("return np.sqrt(x)", "return x * 2.0") + plain = NAN.replace("return np.sqrt(x + 500.0)", "return x * 2.0") verdict = judge(tmp_path, plain, SimpleNamespace(w_probe=lambda x: x * 2.0)) assert verdict.confidence is Confidence.BIT_EXACT, verdict.detail assert verdict.metrics["subprograms"]["probe"]["redrawn"] == 0 diff --git a/tests/test_fortran_analysis.py b/tests/test_fortran_analysis.py index a5c048c..9a45dbf 100644 --- a/tests/test_fortran_analysis.py +++ b/tests/test_fortran_analysis.py @@ -1264,6 +1264,43 @@ def test_a_write_only_f77_dummy_is_given_the_intent_its_use_shows(tmp_path: Path assert intents["onward"] == "UNKNOWN" # only passed on; its fate is the callee's +def test_a_dummy_handed_to_what_might_write_it_is_not_read_only(tmp_path: Path) -> None: + """Read-only is proved, not assumed. A dummy passed whole to a function + this file does not define (a use-associated one parses exactly like a + subscript), written as an internal unit, or aliased by an ASSOCIATE stays + UNKNOWN; a subscript of a declared array and an expression actual, which + is a temporary, are reads.""" + source = """ +module escapes + use elsewhere, only: g + implicit none + real, dimension(8) :: tbl +contains + subroutine rates(a, b, c, buf, d, e, out) + real :: a, b, c, d, e + integer :: buf + real :: out + real :: local(4) + local = 0.0 + out = g(a) + tbl(int(b)) + local(int(c)) + g(e + 1.0) + write(buf, '(i5)') 3 + associate (alias => d) + alias = 1.0 + end associate + end subroutine rates +end module escapes +""" + record = interface.extract(_write(tmp_path, "escapes.f90", source), kind_assumptions=KINDS) + intents = {arg["name"]: arg["intent"] for arg in record["subprograms"][0]["args"]} + assert intents["a"] == "UNKNOWN" # g is not this file's; it may write a + assert intents["b"] == "IN" # a subscript of a module array + assert intents["c"] == "IN" # a subscript of a local array + assert intents["buf"] == "UNKNOWN" # the internal unit of a WRITE + assert intents["d"] == "UNKNOWN" # assigned through its ASSOCIATE alias + assert intents["e"] == "IN" # ``e + 1.0`` is a temporary; g cannot write e + assert intents["out"] == "OUT" + + def test_a_module_allocatable_records_the_lower_bound_its_allocate_gave_it( tmp_path: Path, ) -> None: From 26792cca67d89fdffbc6502d25284d5f40950c06 Mon Sep 17 00:00:00 2001 From: lewisychen Date: Fri, 4 Sep 2026 22:50:59 -0600 Subject: [PATCH 2/2] Re-record the corpus baseline: chkder is named for its declined draws minpack's chkder takes two of the eight values the default integer range draws for mode, and was compared on the survivors: 19 draws declined against 10 trials. It is now named in the unit's detail with the reason and the remedy (pin mode with `ranges`); the unit was already failed on dogleg's moved extents. No other unit moves; the per-subprogram metrics gain `declined` by reason. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HYzzevwvTuLBMYjzdHUhGg --- corpus/baseline.json | 49 +++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/corpus/baseline.json b/corpus/baseline.json index 48e6dfb..19d8424 100644 --- a/corpus/baseline.json +++ b/corpus/baseline.json @@ -913,32 +913,29 @@ "stopped_by": "differential.bitexact", "verdicts": { "differential.bitexact": { - "detail": "2 subprogram(s) could not be compared: dogleg: 10 of 10 trial(s) were compared only after the free extent(s) lr, n were moved off the configured values; the 45 ", + "detail": "3 subprogram(s) could not be compared: chkder: 19 draw(s) were declined (19 error stop) to compare 10 trial(s); the trials compared are a minority of what the c", "metrics": { - "bit_exact": 17270, + "bit_exact": 17110, + "declined": { + "NaN on both sides": 7 + }, "integer_mismatch": 0, "integer_points": 760, "max_rel": 0.0, "max_ulp": 0, "nan_mismatch": 0, - "points": 17270, + "points": 17110, + "redrawn": 7, "subprograms": { "chkder": { - "bit_exact": 160, - "integer_mismatch": 0, - "integer_points": 0, - "max_rel": 0.0, - "max_ulp": 0, - "nan_mismatch": 0, - "points": 160, - "redrawn": 19, - "reshaped": 0 + "error": "19 draw(s) were declined (19 error stop) to compare 10 trial(s); the trials compared are a minority of what the configured draw produces and are not evidence about the rest. Narrow the draw with `ranges`, or pin `dims`, to values the subprogram takes" }, "dogleg": { "error": "10 of 10 trial(s) were compared only after the free extent(s) lr, n were moved off the configured values; the 45 point(s) that fit are not evidence at those extents. Pin `dims` to extents the subprogram takes" }, "enorm": { "bit_exact": 10, + "declined": {}, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, @@ -950,6 +947,7 @@ }, "fdjac1": { "bit_exact": 890, + "declined": {}, "integer_mismatch": 0, "integer_points": 10, "max_rel": 0.0, @@ -961,6 +959,7 @@ }, "fdjac2": { "bit_exact": 810, + "declined": {}, "integer_mismatch": 0, "integer_points": 10, "max_rel": 0.0, @@ -972,6 +971,7 @@ }, "hybrd": { "bit_exact": 1380, + "declined": {}, "integer_mismatch": 0, "integer_points": 20, "max_rel": 0.0, @@ -983,6 +983,7 @@ }, "hybrd1": { "bit_exact": 250, + "declined": {}, "integer_mismatch": 0, "integer_points": 10, "max_rel": 0.0, @@ -994,6 +995,7 @@ }, "hybrj": { "bit_exact": 1390, + "declined": {}, "integer_mismatch": 0, "integer_points": 30, "max_rel": 0.0, @@ -1005,6 +1007,7 @@ }, "hybrj1": { "bit_exact": 890, + "declined": {}, "integer_mismatch": 0, "integer_points": 10, "max_rel": 0.0, @@ -1016,6 +1019,7 @@ }, "lmder": { "bit_exact": 1390, + "declined": {}, "integer_mismatch": 0, "integer_points": 110, "max_rel": 0.0, @@ -1027,6 +1031,7 @@ }, "lmder1": { "bit_exact": 970, + "declined": {}, "integer_mismatch": 0, "integer_points": 90, "max_rel": 0.0, @@ -1038,6 +1043,7 @@ }, "lmdif": { "bit_exact": 1380, + "declined": {}, "integer_mismatch": 0, "integer_points": 100, "max_rel": 0.0, @@ -1049,6 +1055,7 @@ }, "lmdif1": { "bit_exact": 330, + "declined": {}, "integer_mismatch": 0, "integer_points": 90, "max_rel": 0.0, @@ -1060,6 +1067,9 @@ }, "lmpar": { "bit_exact": 970, + "declined": { + "NaN on both sides": 7 + }, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, @@ -1071,6 +1081,7 @@ }, "lmstr": { "bit_exact": 1390, + "declined": {}, "integer_mismatch": 0, "integer_points": 110, "max_rel": 0.0, @@ -1082,6 +1093,7 @@ }, "lmstr1": { "bit_exact": 970, + "declined": {}, "integer_mismatch": 0, "integer_points": 90, "max_rel": 0.0, @@ -1093,6 +1105,7 @@ }, "qform": { "bit_exact": 720, + "declined": {}, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, @@ -1104,6 +1117,7 @@ }, "qrfac": { "bit_exact": 960, + "declined": {}, "integer_mismatch": 0, "integer_points": 80, "max_rel": 0.0, @@ -1115,6 +1129,7 @@ }, "qrsolv": { "bit_exact": 880, + "declined": {}, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, @@ -1126,6 +1141,7 @@ }, "r1mpyq": { "bit_exact": 640, + "declined": {}, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, @@ -1140,6 +1156,7 @@ }, "rwupdt": { "bit_exact": 890, + "declined": {}, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, @@ -1318,15 +1335,18 @@ "detail": "1 translated subprogram(s) were never compared: print_msg -- defer them or drop them from the unit; silence is not a pass", "metrics": { "bit_exact": 10, + "declined": {}, "integer_mismatch": 0, "integer_points": 10, "max_rel": 0.0, "max_ulp": 0, "nan_mismatch": 0, "points": 10, + "redrawn": 0, "subprograms": { "is_inf": { "bit_exact": 10, + "declined": {}, "integer_mismatch": 0, "integer_points": 10, "max_rel": 0.0, @@ -1914,15 +1934,18 @@ "detail": "80 points across 1 subprogram(s), all bit-exact", "metrics": { "bit_exact": 80, + "declined": {}, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, "max_ulp": 0, "nan_mismatch": 0, "points": 80, + "redrawn": 0, "subprograms": { "sort": { "bit_exact": 80, + "declined": {}, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, @@ -2061,12 +2084,14 @@ "detail": "11 subprogram(s) could not be compared: dchfdv: oracle raised: ValueError: failed to create intent(cache|hide)|optional array-- must have defined dimensions but", "metrics": { "bit_exact": 0, + "declined": {}, "integer_mismatch": 0, "integer_points": 0, "max_rel": 0.0, "max_ulp": 0, "nan_mismatch": 0, "points": 0, + "redrawn": 0, "subprograms": { "dchfdv": { "error": "oracle raised: ValueError: failed to create intent(cache|hide)|optional array-- must have defined dimensions but got (-1,)"