Skip to content

Commit

Permalink
Client: Split trial.json into diff files (#129)
Browse files Browse the repository at this point in the history
### Summary

Export circuits and texts from trial json into diff files.
Details about folder organization : #122 

### Details and comments

- [x] Reorganize into `trial_uuid` folders
- [x] Simply to have any get using the `get()` function and any save to use the `save()` function
- [x] Export circuits
- [x] Export texts
- [x] Update unit tests

---
Closes #122
  • Loading branch information
mickahell authored Mar 18, 2024
1 parent 1b1a05e commit 20bdfd6
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 29 deletions.
81 changes: 57 additions & 24 deletions client/purplecaffeine/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import json
import logging
import os
import re
import copy
from pathlib import Path
from typing import Optional, Union, List, Any, Dict
from uuid import uuid4
Expand All @@ -16,6 +18,7 @@
from qiskit import __version__
from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info.operators import Operator
from qiskit_ibm_runtime.utils import RuntimeEncoder

from purplecaffeine.exception import PurpleCaffeineException
from purplecaffeine.helpers import Configuration
Expand Down Expand Up @@ -50,7 +53,7 @@ def __init__(
circuits: Optional[List[List[Union[str, QuantumCircuit]]]] = None,
operators: Optional[List[List[Union[str, Operator]]]] = None,
artifacts: Optional[List[List[str]]] = None,
texts: Optional[List[List[str]]] = None,
texts: Optional[List[List[Union[str, str]]]] = None,
arrays: Optional[List[List[Union[str, np.ndarray]]]] = None,
tags: Optional[List[str]] = None,
versions: Optional[List[List[str]]] = None,
Expand Down Expand Up @@ -223,24 +226,20 @@ def read(self, trial_id: str) -> Trial:
return self.storage.get(trial_id=trial_id)

@staticmethod
def import_from_shared_file(path) -> Trial:
def import_from_shared_file(path: str, trial_id: str) -> Trial:
"""Import Trial for shared file.
Args:
path: full path of the file
trial_id: trial id of the folder
Returns:
Trial dict object
"""
with open(os.path.join(path), "r", encoding="utf-8") as trial_file:
trial_json = json.load(trial_file, cls=TrialDecoder)
if "id" in trial_json:
del trial_json["id"]
if "uuid" in trial_json:
del trial_json["uuid"]
return Trial(**trial_json)

def export_to_shared_file(self, path) -> str:
return LocalStorage(path).get(trial_id=trial_id)

def export_to_shared_file(self, path: str) -> str:
"""Export trial to shared file.
Args:
Expand All @@ -249,11 +248,10 @@ def export_to_shared_file(self, path) -> str:
Returns:
Full path of the file
"""
filename = os.path.join(path, f"{self.uuid}.json")
with open(filename, "w", encoding="utf-8") as trial_file:
json.dump(self.__dict__, trial_file, cls=TrialEncoder, indent=4)
self.storage.path = path
self.storage.save(trial=self)

return filename
return os.path.join(path, f"trial_{self.uuid}")

def __exit__(self, exc_type, exc_val, exc_tb):
self.add_version("qiskit", __version__)
Expand Down Expand Up @@ -478,8 +476,25 @@ def save(self, trial: Trial) -> str:
Returns:
self.path: path of the trial file
"""
save_path = os.path.join(self.path, f"{trial.uuid}.json")
with open(save_path, "w", encoding="utf-8") as trial_file:
save_path = os.path.join(self.path, f"trial_{trial.uuid}")
if not os.path.isdir(save_path):
os.makedirs(save_path)

for circuit in trial.circuits:
save_circuit = os.path.join(save_path, f"circuit_{circuit[0]}.json")
with open(save_circuit, "w", encoding="utf-8") as circuit_file:
json.dump(circuit, circuit_file, cls=RuntimeEncoder, indent=4)
circuit[1] = f"Check the circuit_{circuit[0]}.json file."

for text in trial.texts:
save_text = os.path.join(save_path, f"text_{text[0]}.json")
with open(save_text, "w", encoding="utf-8") as text_file:
json.dump(text, text_file, cls=RuntimeEncoder, indent=4)
text[1] = f"Check the text_{text[0]}.json file."

with open(
os.path.join(save_path, "trial.json"), "w", encoding="utf-8"
) as trial_file:
json.dump(trial.__dict__, trial_file, cls=TrialEncoder, indent=4)

return self.path
Expand All @@ -493,15 +508,29 @@ def get(self, trial_id: str) -> Trial:
Returns:
trial: object of a trial
"""
trial_path = os.path.join(self.path, f"{trial_id}.json")
if not os.path.isfile(trial_path):
trial_path = os.path.join(self.path, f"trial_{trial_id}")
if not os.path.isfile(os.path.join(trial_path, "trial.json")):
logging.warning(
"Your file %s does not exist.",
trial_path,
)
raise ValueError(trial_id)
with open(trial_path, "r", encoding="utf-8") as trial_file:
return Trial(**json.load(trial_file, cls=TrialDecoder))
with open(
os.path.join(trial_path, "trial.json"), "r", encoding="utf-8"
) as trial_file:
trial = Trial(**json.load(trial_file, cls=TrialDecoder))

for index, circuit in enumerate(copy.copy(trial.circuits)):
circ_path = os.path.join(trial_path, f"circuit_{circuit[0]}.json")
with open(circ_path, "r", encoding="utf-8") as circ_file:
trial.circuits[index] = json.load(circ_file, cls=TrialDecoder)

for index, text in enumerate(copy.copy(trial.texts)):
text_path = os.path.join(trial_path, f"text_{text[0]}.json")
with open(text_path, "r", encoding="utf-8") as text_file:
trial.texts[index] = json.load(text_file, cls=TrialDecoder)

return trial

def list(
self,
Expand All @@ -524,13 +553,17 @@ def list(
offset = offset or 0
limit = limit or 10

trials_path = glob.glob(f"{self.path}/**.json")
trials_path = glob.glob(f"{self.path}/trial_*")
trials_path.sort(key=os.path.getmtime, reverse=True)
trials = []
for path in trials_path:
with open(path, "r", encoding="utf-8") as trial_file:
trial_dict = json.load(trial_file, cls=TrialDecoder)
trials.append(Trial(**trial_dict))
trials.append(
self.get(
trial_id=re.search(r"trial_([^/]+)", os.path.basename(path)).group(
1
)
)
)

if query:
trials = [
Expand Down
3 changes: 3 additions & 0 deletions client/purplecaffeine/utils/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Any

from qiskit.providers import Backend
from qiskit.circuit import QuantumCircuit
from qiskit_ibm_runtime.utils import RuntimeEncoder, RuntimeDecoder


Expand All @@ -21,6 +22,8 @@ def default(self, obj: Any) -> Any:
}
elif isinstance(obj, BaseStorage):
return {"__type__": "PurpleCaffeineStorage"}
elif isinstance(obj, QuantumCircuit):
return None
return super().default(obj)


Expand Down
7 changes: 6 additions & 1 deletion client/tests/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import shutil
from pathlib import Path
from unittest import TestCase
from qiskit import QuantumCircuit
from testcontainers.compose import DockerCompose
from testcontainers.localstack import LocalStackContainer

Expand All @@ -28,12 +29,16 @@ def test_save_get_list_local_storage(self):
# Save
self.local_storage.save(trial=self.my_trial)
self.assertTrue(
os.path.isfile(os.path.join(self.save_path, f"{self.my_trial.uuid}.json"))
os.path.isfile(
os.path.join(self.save_path, f"trial_{self.my_trial.uuid}/trial.json")
)
)
# Get
recovered = self.local_storage.get(trial_id=self.my_trial.uuid)
self.assertTrue(isinstance(recovered, Trial))
self.assertEqual(recovered.parameters, [["test_parameter", "parameter"]])
self.assertEqual(recovered.circuits, [["test_circuit", QuantumCircuit(2)]])
self.assertEqual(recovered.texts, [["test_text", "text"]])
with self.assertRaises(ValueError):
self.local_storage.get(trial_id="999")
# List
Expand Down
12 changes: 8 additions & 4 deletions client/tests/test_trial.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ def test_trial_context(self):
with Trial(name="test_trial", storage=self.local_storage, uuid=uuid) as trial:
trial.add_metric("test_metric", 42)
trial.read(trial_id=uuid)
self.assertTrue(os.path.isfile(os.path.join(self.save_path, f"{uuid}.json")))
self.assertTrue(
os.path.isfile(os.path.join(self.save_path, f"trial_{uuid}/trial.json"))
)
self.assertEqual(trial.metrics, [["test_metric", 42]])
self.assertEqual(trial.versions, [["qiskit", __version__]])

Expand All @@ -76,7 +78,9 @@ def test_save_read_local_trial(self):
trial.save()

self.assertTrue(
os.path.isfile(os.path.join(self.save_path, f"{trial.uuid}.json"))
os.path.isfile(
os.path.join(self.save_path, f"trial_{trial.uuid}/trial.json")
)
)
recovered = trial.read(trial_id=trial.uuid)
self.assertEqual(recovered.description, "Short desc")
Expand All @@ -95,11 +99,11 @@ def test_export_import(self):
# Export
trial.export_to_shared_file(path=self.save_path)
self.assertTrue(
os.path.isfile(os.path.join(self.save_path, f"{trial.uuid}.json"))
os.path.isdir(os.path.join(self.save_path, f"trial_{trial.uuid}"))
)
# Import
new_trial = Trial("test_import").import_from_shared_file(
os.path.join(self.save_path, f"{trial.uuid}.json")
self.save_path, trial.uuid
)
self.assertEqual(new_trial.description, "Short desc")
self.assertEqual(new_trial.metrics, [["test_metric", 42]])
Expand Down
12 changes: 12 additions & 0 deletions client/tests/utils/test_json.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Tests for Json."""
import json
import copy
from unittest import TestCase

from qiskit_ibm_runtime.utils import RuntimeEncoder

from purplecaffeine.core import Trial
from purplecaffeine.utils import TrialEncoder, TrialDecoder

Expand All @@ -17,9 +20,18 @@ def test_encoder_decoder(self):
# Encoder
trial_encode = json.dumps(my_trial.__dict__, cls=TrialEncoder, indent=4)
self.assertTrue(isinstance(trial_encode, str))
for circuit in my_trial.circuits:
circ_encode = json.dumps(circuit, cls=RuntimeEncoder, indent=4)
for text in my_trial.texts:
text_encode = json.dumps(text, cls=RuntimeEncoder, indent=4)

# Decoder
trial_decode = Trial(**json.loads(trial_encode, cls=TrialDecoder))
for index, circuit in enumerate(copy.copy(trial_decode.circuits)):
trial_decode.circuits[index] = json.loads(circ_encode, cls=TrialDecoder)
for index, text in enumerate(copy.copy(trial_decode.texts)):
trial_decode.texts[index] = json.loads(text_encode, cls=TrialDecoder)

self.assertTrue(isinstance(trial_decode, Trial))
self.assertEqual(trial_decode.metrics, my_trial.metrics)
self.assertEqual(trial_decode.parameters, my_trial.parameters)
Expand Down

0 comments on commit 20bdfd6

Please sign in to comment.