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

Improving simulations.py input options #111

Merged
merged 23 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
12 changes: 12 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

### New features since the last release

* The I/O of frontend `simulations.py` has been improved. The input has been simplified to an intuitive set of `code`, `code_args`, `noise`, and `noise_args`. As long as those combinations are valid FlamingPy will run error corrections and automatically set up the output file based on inputs. [#111](https://github.com/XanaduAI/flamingpy/pull/111) (backward incompatible)
nariman87 marked this conversation as resolved.
Show resolved Hide resolved
* An example run will be
```
python flamingpy/simulations.py -code "SurfaceCode" -code_args "{'distance':3, 'ec':'primal', 'boundaries':'open'}" -noise "CVLayer" -noise_args "{'delta':0.09, 'p_swap':0.25}" -decoder "MWPM" -decoder_args "{'weight_opts':{'method':'blueprint', 'integer':False, 'multiplier':1, 'delta':0.09}}" -trials 100
```
which generates
```
code,distance,ec,boundaries,noise,delta,p_swap,decoder,weight_opts,errors,trials,current_time,simulation_time,mpi_size
SurfaceCode,3,primal,open,CVLayer,0.09,0.25,MWPM,{'method': 'blueprint', 'integer': False, 'multiplier': 1, 'delta': 0.09},10,100,00:15:50,0.370795,1
```
ilan-tz marked this conversation as resolved.
Show resolved Hide resolved

### Bug fixes

Expand All @@ -16,6 +26,8 @@

This release contains contributions from (in alphabetical order):

Nariman Saadatmand

See full commit details ...


Expand Down
2 changes: 1 addition & 1 deletion flamingpy/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
"""Version number (major.minor.patch[label])"""


__version__ = "0.9.1b0"
__version__ = "0.9.1b0.dev0"
71 changes: 36 additions & 35 deletions flamingpy/decoders/decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,42 +55,43 @@ def assign_weights(code, decoder, **kwargs):

# Blueprint weight assignment or weighted-union-find weight assignment
# dependent on the type of neighbours.
if weight_options.get("method") == "blueprint" and decoder == "MWPM":
for node in qubit_coords:
neighbors = G[node]
# Obtain the list and the number of p-squeezed states in the neighborhood of the node.
p_list = [G.nodes[v]["state"] for v in neighbors if G.nodes[v]["state"] == "p"]
p_count = len(p_list)
if p_count in (0, 1):
if weight_options.get("prob_precomputed"):
err_prob = G.nodes[node]["p_phase_cond"]
else:
delta_effective = (len(neighbors) + 1) * weight_options.get("delta")
hom_val = G.nodes[node]["hom_val_p"]
err_prob = Z_err_cond(delta_effective, hom_val)
# Allow for taking log of 0.
err_prob = min(err_prob, 0.5)
# TODO: Can I just choose an arbitrary small number?
if err_prob == 0:
err_prob = smallest_number
if weight_options.get("integer"):
multiplier = weight_options.get("multiplier")
weight = round(-multiplier * np.log(err_prob))
else:
weight = -np.log(err_prob)
G.nodes[node]["weight"] = weight
else:
# Dictionary of the form number of swapouts: error probability.
weight_dict = {2: 1 / 4, 3: 1 / 3, 4: 2 / 5}
err_prob = weight_dict[p_count]
if weight_options.get("integer"):
multiplier = weight_options.get("multiplier")
weight = round(-multiplier * np.log(err_prob))
if weight_options.get("method") == "blueprint":
if decoder == "MWPM":
for node in qubit_coords:
neighbors = G[node]
# Obtain the list and the number of p-squeezed states in the neighborhood of the node.
p_list = [G.nodes[v]["state"] for v in neighbors if G.nodes[v]["state"] == "p"]
p_count = len(p_list)
if p_count in (0, 1):
if weight_options.get("prob_precomputed"):
err_prob = G.nodes[node]["p_phase_cond"]
else:
delta_effective = (len(neighbors) + 1) * weight_options.get("delta")
hom_val = G.nodes[node]["hom_val_p"]
err_prob = Z_err_cond(delta_effective, hom_val)
# Allow for taking log of 0.
err_prob = min(err_prob, 0.5)
# TODO: Can I just choose an arbitrary small number?
if err_prob == 0:
err_prob = smallest_number
if weight_options.get("integer"):
multiplier = weight_options.get("multiplier")
weight = round(-multiplier * np.log(err_prob))
else:
weight = -np.log(err_prob)
G.nodes[node]["weight"] = weight
else:
weight = -np.log(err_prob)
G.nodes[node]["weight"] = weight
if decoder == "UF":
raise Exception("Incompatible decoder/weight options.")
# Dictionary of the form number of swapouts: error probability.
weight_dict = {2: 1 / 4, 3: 1 / 3, 4: 2 / 5}
err_prob = weight_dict[p_count]
if weight_options.get("integer"):
multiplier = weight_options.get("multiplier")
weight = round(-multiplier * np.log(err_prob))
else:
weight = -np.log(err_prob)
G.nodes[node]["weight"] = weight
elif decoder == "UF":
raise Exception("Incompatible decoder & weight options combination.")
# Naive weight assignment, unity weights.
elif weight_options.get("method") == "uniform":
if decoder == "UF":
Expand Down
175 changes: 61 additions & 114 deletions flamingpy/simulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
# pylint: disable=wrong-import-position,consider-using-with

import argparse
import csv
import sys
import warnings
import logging
from ast import literal_eval as l_eval
ilan-tz marked this conversation as resolved.
Show resolved Hide resolved

from datetime import datetime
from time import perf_counter
Expand All @@ -43,22 +43,27 @@
from flamingpy.noise import CVLayer, CVMacroLayer, IidNoise


noise_dict = {"blueprint": CVLayer, "passive": CVMacroLayer, "iid": IidNoise}
reverse_noise_dict = {b: a for a, b in noise_dict.items()}
str_dict = {
"SurfaceCode": SurfaceCode,
"CVLayer": CVLayer,
"CVMacroLayer": CVMacroLayer,
"IidNoise": IidNoise,
}
# reverse_noise_dict = {b: a for a, b in noise_dict.items()}
nariman87 marked this conversation as resolved.
Show resolved Hide resolved


def ec_mc_trial(
code_instance,
noise_instance,
decoder,
weight_options,
weight_opts,
rng=default_rng(),
):
"""Runs a single trial of Monte Carlo simulations of error-correction for
the given code."""
nariman87 marked this conversation as resolved.
Show resolved Hide resolved
noise_instance.apply_noise(rng)

result = correct(code=code_instance, decoder=decoder, weight_options=weight_options)
result = correct(code=code_instance, decoder=decoder, weight_options=weight_opts)

return result

Expand Down Expand Up @@ -86,7 +91,7 @@ def ec_monte_carlo(
noise_instance (noise object): the initialized noise layer
(CVLayer, CVMacroLayer, or IidNoise)
decoder (str): the decoding algorithm ("MWPM" or "UF")
deocder_args (dict): arguments for the decoder (such as weight options)
weight_opts (dict): weight options for the decoder
nariman87 marked this conversation as resolved.
Show resolved Hide resolved
world_comm, mpi_rank, mpi_size: arguments for the MPI library.

Returns:
Expand Down Expand Up @@ -121,46 +126,16 @@ def ec_monte_carlo(


def run_ec_simulation(
trials, code, code_args, noise, noise_args, decoder, decoder_args=None, fname=None
trials, code, code_args, noise, noise_args, decoder, decoder_args, fname=None
):
"""Run full Monte Carlo error-correction simulations."""
# Set up the objects common to all trials.
if decoder_args is None:
decoder_args = {}
# time the simulation
simulation_start_time = perf_counter()

# Instance of the qubit QEC code
code_instance = code(**code_args)
noise_instance = noise(code_instance, **noise_args)

# For the blueprint
if noise == CVLayer:
if decoder == "MWPM":
weight_opts = decoder_args.get("weight_opts")
if weight_opts is None:
weight_opts = {}
default_weight_opts = {
"method": "blueprint",
"integer": False,
"multiplier": 1,
"delta": noise_args.get("delta"),
}
weight_opts = {**default_weight_opts, **weight_opts}
else:
weight_opts = None

# For the passive architecture
elif noise == CVMacroLayer:
if decoder == "MWPM":
weight_opts = {"method": "blueprint", "prob_precomputed": True}
else:
weight_opts = None

# For iid Z errors
elif noise == IidNoise:
weight_opts = {"method": "uniform"}

decoder_args.update({"weight_opts": weight_opts})

if "MPI" in globals():
world_comm = MPI.COMM_WORLD
mpi_size = world_comm.Get_size()
Expand All @@ -170,8 +145,6 @@ def run_ec_simulation(
mpi_size = 1
mpi_rank = 0

# Perform and time the simulation
simulation_start_time = perf_counter()
errors = ec_monte_carlo(
trials,
code_instance,
Expand All @@ -188,52 +161,42 @@ def run_ec_simulation(
# Store results in the provided file-path or by default in
# a .sims_data directory in the file simulations_results.csv.
file_name = fname or "./flamingpy/.sims_data/sims_results.csv"

# Create a CSV file if it doesn't already exist.
try:
file = open(file_name, "x", newline="", encoding="utf8")
writer = csv.writer(file)
writer.writerow(
[
"noise",
"distance",
"ec",
"boundaries",
"delta",
"p_swap",
"error_probability",
"decoder",
"errors",
"trials",
"current_time",
"simulation_time",
"mpi_size",
]
)
file.write("code,")
for key in code_args.keys():
file.write("%s," % (key))
file.write("noise,")
for key in noise_args.keys():
file.write("%s," % (key))
file.write("decoder,")
for key in decoder_args.keys():
file.write("%s," % (key))
nariman87 marked this conversation as resolved.
Show resolved Hide resolved
file.write("errors,trials,current_time,simulation_time,mpi_size\n")
# Open the file for appending if it already exists.
except FileExistsError:
file = open(file_name, "a", newline="", encoding="utf8")
writer = csv.writer(file)
# writer = csv.writer(file)
file.write("%s," % (code.__name__))
for value in code_args.values():
file.write("%s," % (value))
file.write("%s," % (noise.__name__))
for value in noise_args.values():
file.write("%s," % (value))
file.write("%s," % (decoder))
for value in decoder_args.values():
file.write("%s," % (value))
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is a small bug here. Every entry of weight_opts is placed in a separate column of the output file, which causes the rest of the entries to be shifted to the right relative to the heading, whereas they are supposed to be placed in just one column.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks, fixed by adding double quotes to that field.

current_time = datetime.now().time().strftime("%H:%M:%S")
for key in ["delta", "p_swap", "error_probability"]:
if key not in noise_args:
noise_args.update({key: "None"})
writer.writerow(
[
reverse_noise_dict[noise],
code_args["distance"],
code_args["ec"],
code_args["boundaries"],
noise_args.get("delta"),
noise_args.get("p_swap"),
noise_args.get("error_probability"),
decoder,
file.write(
"%i,%i,%s,%f,%i\n"
% (
errors,
trials,
current_time,
(simulation_stop_time - simulation_start_time),
mpi_size,
]
)
)
file.close()

Expand All @@ -242,42 +205,35 @@ def run_ec_simulation(
if len(sys.argv) != 1:
# Parsing input parameters
parser = argparse.ArgumentParser(description="Arguments for Monte Carlo FT simulations.")
parser.add_argument("-code", type=str)
parser.add_argument("-code_args", type=str)
parser.add_argument("-noise", type=str)
parser.add_argument("-distance", type=int)
parser.add_argument("-ec", type=str)
parser.add_argument("-boundaries", type=str)
parser.add_argument("-delta", type=float)
parser.add_argument("-pswap", type=float)
parser.add_argument("-errprob", type=float)
parser.add_argument("-trials", type=int)
parser.add_argument("-noise_args", type=str)
parser.add_argument("-decoder", type=str)

parser.add_argument("-decoder_args", type=str)
parser.add_argument("-trials", type=int)
args = parser.parse_args()
params = {
"code": args.code,
"code_args": args.code_args,
"noise": args.noise,
"distance": args.distance,
"ec": args.ec,
"boundaries": args.boundaries,
"delta": args.delta,
"p_swap": args.pswap,
"error_probability": args.errprob,
"trials": args.trials,
"noise_args": args.noise_args,
"decoder": args.decoder,
"decoder_args": args.decoder_args,
"trials": args.trials,
}

else:
# User can specify values here, if not using command line.
params = {
"noise": "passive",
"distance": 3,
"ec": "primal",
"boundaries": "open",
"delta": 0.09,
"p_swap": 0.25,
"error_probability": 0.1,
"trials": 100,
"code": "SurfaceCode",
"code_args": "{'distance':3, 'ec':'primal', 'boundaries':'open'}",
"noise": "CVMacroLayer",
"noise_args": "{'delta':0.09, 'p_swap':0.25}",
"decoder": "MWPM",
"decoder_args": "{'weight_opts':{'method':'blueprint', 'prob_precomputed':True}}",
"trials": 100,
ilan-tz marked this conversation as resolved.
Show resolved Hide resolved
}

# Checking that a valid decoder choice is provided
if params["decoder"].lower() in ["unionfind", "uf", "union-find", "union find"]:
params["decoder"] = "UF"
Expand All @@ -286,25 +242,16 @@ def run_ec_simulation(
else:
raise ValueError(f"Decoder {params['decoder']} is either invalid or not yet implemented.")

# The Monte Carlo simulations
code = SurfaceCode
code_args = {key: params[key] for key in ["distance", "ec", "boundaries"]}

noise = noise_dict[params["noise"]]
if params.get("noise") == "iid":
if params.get("error_probability") is None:
raise ValueError("No argument `err_prob` found for `iid` noise.")
noise_args = {"error_probability": params.get("error_probability")}
else:
noise_args = {key: params[key] for key in ["delta", "p_swap"]}
noise = str_dict[params["noise"]]
code = str_dict[params["code"]]
nariman87 marked this conversation as resolved.
Show resolved Hide resolved

decoder = params["decoder"]
args = {
"trials": params["trials"],
"code": code,
ilan-tz marked this conversation as resolved.
Show resolved Hide resolved
"code_args": code_args,
"code_args": l_eval(params["code_args"]),
"noise": noise,
ilan-tz marked this conversation as resolved.
Show resolved Hide resolved
"noise_args": noise_args,
"decoder": decoder,
"noise_args": l_eval(params["noise_args"]),
"decoder": params["decoder"],
"decoder_args": l_eval(params["decoder_args"]),
}
run_ec_simulation(**args)
Loading