Source code for scqubits.core.circuit_input

import pyparsing as pp
import os

from typing import List, Tuple
from scqubits.utils.misc import is_string_float
from pyparsing import Group, Opt, Or, Literal, Suppress
import numpy as np
import scipy as sp
import sympy as sm

# *****************************************************************
#  OUR GRAMMAR DEFINITIONS
# *****************************************************************
#
# Pattern for branch definitions in yaml file:
#      - [ <branch_type>, <node1>, <node2>, <param>, <aux params> ]       or
#      - [ <branch_type>, <node1>, <node2>, <param1>, <param2> ]
#
# where <param>:   <symbol> = <number>
#                  <number>
#                  <symbol>
#
# The last option is valid only if <symbol> has previously been
# assigned. Optionally, <number> may be grouped with valid physical
# unit.


# - Ignore in parsing ********************************************
# Mark the following as characters / character combinations not
# to be recorded in the parsed result. I.e., the grammar may expect
# these in various places, but they are not carried forward into
# the parsed result
BEG = Suppress(Literal("-") + Literal("["))
END = Suppress("]")
CM = Suppress(",")
QM = Opt(Suppress('"'))  # optional quotation mark: may use JJ, or "JJ"


# - Numbers ******************************************************
INT = pp.common.integer  # unsigned integer
NUM = pp.common.fnumber  # float


# - Branch types ***************************************************
branch_type_names = ["C", "L"]

# build up dictionary of branch types
# allow, for example, both "JJ" as well as just JJ
BRANCH_TYPES = {name: QM + name + QM for name in branch_type_names}
for BTYPE in BRANCH_TYPES.values():
    BTYPE.set_results_name("branch_type")

JJ_ORDER = pp.Word(pp.nums).add_condition(
    lambda tokens: int(tokens[0]) > 0, message="Junction order must be greater than 0."
)
BRANCH_TYPES["JJ"] = (
    QM + pp.Combine(pp.Word("JJ") + Opt(JJ_ORDER) + Opt(pp.Word("s"))) + QM
)  # defining grammar to find "JJi" where i is an optional natural number


# - Units: prefixes etc. **************************************************
prefix_dict = {
    "T": 1e12,  # Tera
    "G": 1e9,  # Giga
    "M": 1e6,  # Mega
    "k": 1e3,  # kilo
    "m": 1e-3,  # milli
    "u": 1e-6,  # micro
    "n": 1e-9,  # nano
    "p": 1e-12,  # pico
    "f": 1e-15,  # femto
}
PREFIX = pp.Char(list(prefix_dict.keys()))

energy_names = ["EJ", "EC", "EL"]


UNITS_FREQ_ENERGY = Literal("Hz") ^ Literal("J") ^ Literal("eV")

UNITS = {name: Opt(PREFIX, None) for name in energy_names}
UNITS["EJ"] += UNITS_FREQ_ENERGY ^ Literal("A")  # Ampere
UNITS["EC"] += UNITS_FREQ_ENERGY ^ Literal("F")  # Farad
UNITS["EL"] += UNITS_FREQ_ENERGY ^ Literal("H")  # Henry
for name, unit in UNITS.items():
    unit.leave_whitespace()  # allow only "kHz", not "k Hz"
    unit.set_name(f"{name}_UNITS")

# - Parameter specifications --------------------------------
SYMBOL = pp.common.identifier
VALUES = {name: NUM + Opt(" ") + Opt(UNITS[name], None) for name in energy_names}
ASSIGNS = {
    name: SYMBOL + Suppress(Literal("=")) + VALUES[name] for name in energy_names
}

PARAMS = {
    name: Or(
        [
            Group(ASSIGNS[name])("ASSIGN"),
            Group(SYMBOL)("SYMBOL"),
            Group(VALUES[name])("VALUE"),
        ]
    )
    for name in energy_names
}  # can specify in three ways

# # - Branch specifications ------------------------------------------------------
aux_val = pp.Word(
    pp.printables.replace("]", "").replace("[", "") + " "
)  # allowing for numerical expressions in auxiliary params
AUX_PARAM = Group(pp.ZeroOrMore(CM + SYMBOL + Suppress(Literal("=")) + aux_val))(
    "AUX_PARAM"
)

order_count = pp.Empty()


[docs]def find_jj_order(str_result: str, location: int, tokens: pp.ParseResults): from scqubits.core.circuit_utils import _junction_order JJ_TYPE = BEG + BRANCH_TYPES["JJ"] JJ_TYPE.add_parse_action(lambda tokens: _junction_order(tokens[0])) return JJ_TYPE.parse_string(str_result)
order_count.set_parse_action(find_jj_order) BRANCH_JJ = ( BEG + BRANCH_TYPES["JJ"]("BRANCH_TYPE") + CM + INT("node1") + CM + INT("node2") + CM + pp.counted_array(PARAMS["EJ"] + CM, int_expr=order_count)("EJ_VALS") + PARAMS["EC"]("EC") + AUX_PARAM + END ) BRANCH_C = ( BEG + BRANCH_TYPES["C"]("BRANCH_TYPE") + CM + INT("node1") + CM + INT("node2") + CM + PARAMS["EC"]("EC") + AUX_PARAM + END ) BRANCH_L = ( BEG + BRANCH_TYPES["L"]("BRANCH_TYPE") + CM + INT("node1") + CM + INT("node2") + CM + PARAMS["EL"]("EL") + AUX_PARAM + END ) BRANCHES = Or([BRANCH_JJ, BRANCH_C, BRANCH_L]) # uncomment to create a html describing the grammar of this language # BRANCHES.create_diagram("branches.html") # - For filtering out only the code specifying the branches -----------------
[docs]def remove_comments(code: str) -> str: return pp.pythonStyleComment.suppress().transformString(code)
[docs]def remove_branchline(code: str) -> str: return Suppress(Literal("branches") + ":").transformString(code)
[docs]def strip_empty_lines(code: str) -> str: return os.linesep.join( [line.lstrip() for line in code.splitlines() if line.lstrip()] )
pp.autoname_elements() # - Parsing and processing ParsedResults data ------------------------------
[docs]def parse_code_line(code_line: str, _branch_count): """ Args: code_line (str): string describing the branch from the input file _branch_count (_type_): the count of the branch in a given circuit Returns: branch_type: str node_idx1: int node_idx2: int params: str aux_params: str _branch_count: int """ pp_result = BRANCHES.parse_string(code_line) branch_type = pp_result.BRANCH_TYPE[0] branch_type, node_idx1, node_idx2, *params, aux_params = pp_result return branch_type, node_idx1, node_idx2, params, aux_params, _branch_count
[docs]def convert_value_to_GHz(val, units): """ Converts a given value and units to energy in GHz. The units are given in a string in the format "pU" where p is an optional multiplier prefix and U is units. For example: "pH", "nA", "fF", "eV" Args: val (float): value in given units units (str): units described in a string with an optional multiplier prefix Raises: ValueError: If the unit is unknown. Returns: float: Energy in GHz """ # all the possible units # capacitance F, inductance H, current A, energy J, frequency Hz, eV # returns value in GHz if units is None: return val # default is GHz prefix = 1 if units[0] is None else prefix_dict[units[0]] val *= prefix unit_str = units[1] h = sp.constants.h e = sp.constants.e Φ0 = sp.constants.h / (2 * e) if unit_str == "Hz": return val * 1e-9 elif unit_str == "J": return val / h * 1e-9 elif unit_str == "eV": return val * 1.602176634e-19 / h * 1e-9 elif unit_str == "F": return e**2 / (2 * val * h) * 1e-9 elif unit_str == "H": return Φ0**2 / (val * h * (2 * np.pi) ** 2) * 1e-9 elif unit_str == "A": return val * Φ0 / (2 * np.pi * h) * 1e-9 else: raise ValueError(f"Unknown unit {unit_str}")
[docs]def process_param(pattern): """ Returns a tuple containing (symbol, value) given a pattern as detected by pyparsing. Either the symbol or the value can be returned to be none, when the symbol is already assigned or no symbol is assigned to the given branch parameter. """ name = pattern.getName() if name == "ASSIGN": sym = sm.symbols(pattern[0]) val = pattern[1] units = None if pattern[-1] == None else pattern[-2:] val_converted = convert_value_to_GHz(val, units) return sym, val_converted if name == "SYMBOL": return sm.symbols(pattern[0]), None if name == "VALUE": units = None if pattern[-1] == None else pattern[-2:] converted_val = convert_value_to_GHz(pattern[0], units) return None, converted_val if name == "AUX_PARAM": num_of_aux_params = int(len(pattern) / 2) aux_params = {} for idx in range(num_of_aux_params): aux_params[pattern[2 * idx]] = ( float(pattern[2 * idx + 1]) if is_string_float(pattern[2 * idx + 1]) else pattern[2 * idx + 1] ) return aux_params