Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSON support for VerificationResults #93

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions autoverify/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
import sys

from autoverify import __version__ as AV_VERSION
from autoverify.util.verifiers import get_all_complete_verifier_names

from .install import (
from autoverify.cli.install import (
check_commit_hashes,
try_install_verifiers,
try_uninstall_verifiers,
)
from autoverify.util.verifiers import get_all_complete_verifier_names


def _build_arg_parser() -> argparse.ArgumentParser:
Expand Down
48 changes: 45 additions & 3 deletions autoverify/portfolio/portfolio_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
VerificationDataResult,
csv_append_verification_result,
init_verification_result_csv,
json_write_verification_result,
)
from autoverify.util.proc import cpu_count, nvidia_gpu_count
from autoverify.util.resources import to_allocation
Expand Down Expand Up @@ -219,6 +220,33 @@ def _csv_log_result(
vdr = VerificationDataResult.from_verification_result(res_d, inst_d)
csv_append_verification_result(vdr, out_csv)

@staticmethod
def _json_log_result(
out_json: Path,
result: CompleteVerificationResult,
instance: VerificationInstance,
verifier: str,
configuration: Configuration,
):
if isinstance(result, Ok):
res_d = result.unwrap()
success = "OK"
elif isinstance(result, Err):
res_d = result.unwrap_err()
success = "ERR"

inst_d = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think these abbrevations like inst_d and vdr are not so readable. I would rather put the full names for readability

"network": instance.network,
"property": instance.property,
"timeout": instance.timeout,
"verifier": verifier,
"config": configuration,
"success": success,
}

vdr = VerificationDataResult.from_verification_result(res_d, inst_d)
json_write_verification_result(vdr, out_json)

@staticmethod
def _vbs_from_cost_dict(cost_dict: _CostDict) -> _VbsResult:
vbs: _VbsResult = {}
Expand Down Expand Up @@ -286,6 +314,7 @@ def verify_instances(
instances: Iterable[VerificationInstance],
*,
out_csv: Path | None = None,
out_json: Path | None = None,
vnncompat: bool = False,
benchmark: str | None = None,
verifier_kwargs: dict[str, dict[str, Any]] | None = None,
Expand All @@ -295,7 +324,8 @@ def verify_instances(

Arguments:
instances: Instances to evaluate.
out_csv: File where the results are written to.
out_csv: CSV File where the results are written to.
out_json: JSON File where the results are written to.
vnncompat: Use some compat kwargs.
benchmark: Only if vnncompat, benchmark name.
verifier_kwargs: Kwargs passed to verifiers.
Expand All @@ -307,11 +337,14 @@ def verify_instances(
if out_csv:
out_csv = out_csv.expanduser().resolve()

if out_json:
out_json = out_json.expanduser().resolve()

results: dict[VerificationInstance, VerificationDataResult] = {}

with concurrent.futures.ThreadPoolExecutor() as executor:
for instance in instances:
logger.info(f"Running portfolio on {str(instance)}")
for index, instance in enumerate(instances):
logger.info(f"{index} - Running portfolio on {str(instance)}")

futures: dict[
Future[CompleteVerificationResult], ConfiguredVerifier
Expand Down Expand Up @@ -359,6 +392,15 @@ def verify_instances(
fut_cv.configuration,
)

if out_json:
self._json_log_result(
out_json,
result,
instance,
fut_cv.verifier,
fut_cv.configuration,
)

return results

def _process_result(
Expand Down
65 changes: 62 additions & 3 deletions autoverify/util/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import csv
import json
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path
Expand All @@ -22,6 +23,7 @@
class VerificationDataResult:
"""_summary_."""

# TODO: Convert files to path objects
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would not put todos in the code. I would rather create an issue from this

network: str
property: str
timeout: int | None
Expand All @@ -30,9 +32,9 @@ class VerificationDataResult:
success: Literal["OK", "ERR"]
result: VerificationResultString
took: float
counter_example: str | tuple[str, str] | None
stderr: str | None
stdout: str | None
counter_example: str | tuple[str, str] | None = None
stderr: str | None = None
stdout: str | None = None

def __post_init__(self):
"""_summary_."""
Expand All @@ -58,6 +60,25 @@ def as_csv_row(self) -> list[str]:
self.stdout or "",
]

def as_json_row(self) -> dict[str, Any]:
"""Convert data to a json item."""
if isinstance(self.counter_example, tuple):
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we still need this, if the counter example is not returned?

self.counter_example = "\n".join(self.counter_example)

return {
"network": str(self.network),
"property": str(self.property),
"timeout": str(self.timeout),
"verifier": self.verifier,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are the verifier, success and result not wrapped into str()?

"config": str(self.config),
"success": self.success,
"result": self.result,
"took": str(self.took),
# "counter_example": self.counter_example or "",
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would not publish commented code

"stderr": self.stderr or "",
"stdout": self.stdout or "",
}

@classmethod
def from_verification_result(
cls,
Expand Down Expand Up @@ -97,6 +118,30 @@ def csv_append_verification_result(
writer.writerow(verification_result.as_csv_row())


def json_write_verification_result(
verification_result: VerificationDataResult, json_path: Path
):
"""_summary_."""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this summary be changed to contain some information?

# Create json file if it does not exist
if not json_path.exists():
with open(str(json_path.expanduser()), "w") as json_file:
json.dump({"instances": []}, json_file)

# Append to json file
with open(str(json_path.expanduser()), "r+") as json_file:
try:
file_data = json.load(json_file)
# Check if file_data is empty
if not file_data:
file_data = {"instances": []}
except json.decoder.JSONDecodeError:
file_data = {"instances": []}

file_data["instances"].append(verification_result.as_json_row())
json_file.seek(0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this necessary?

json.dump(file_data, json_file)


def read_verification_result_from_csv(
csv_path: Path,
) -> list[VerificationDataResult]:
Expand All @@ -111,6 +156,20 @@ def read_verification_result_from_csv(
return verification_results


def read_verification_result_from_json(
json_path: Path,
) -> list[VerificationDataResult]:
"""Reads a verification results json to a list of its dataclass."""
results = pd.read_json(json_path)["instances"].tolist()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we maybe use the json package for parsing the json here? Pandas can quite easily break for parsing jsons. Also were will an error occuring here be catched?


verification_results: list[VerificationDataResult] = []

for res in results:
verification_results.append(VerificationDataResult(**res))

return verification_results


def write_verification_results_to_csv(results: pd.DataFrame, csv_path: Path):
"""Writes a verification results df to a csv."""
results.to_csv(csv_path, index=False)
Expand Down
Loading