Module circuitgraph.props
Functions for analysis of Boolean and circuit properties.
Examples
>>> import circuitgraph as cg
>>> c = cg.Circuit()
>>> c.add("i0", "input")
'i0'
>>> c.add("i1", "input")
'i1'
>>> c.add("g0", "or", fanin=["i0", "i1"])
'g0'
>>> c.add("g1", "not", fanin=["g0"])
'g1'
>>> cg.props.signal_probability(c, "g0", approx=False)
0.75
>>> cg.props.signal_probability(c, "g1", approx=False)
0.25
Expand source code
"""
Functions for analysis of Boolean and circuit properties.
Examples
--------
>>> import circuitgraph as cg
>>> c = cg.Circuit()
>>> c.add("i0", "input")
'i0'
>>> c.add("i1", "input")
'i1'
>>> c.add("g0", "or", fanin=["i0", "i1"])
'g0'
>>> c.add("g1", "not", fanin=["g0"])
'g1'
>>> cg.props.signal_probability(c, "g0", approx=False)
0.75
>>> cg.props.signal_probability(c, "g1", approx=False)
0.25
"""
from pathlib import Path
import circuitgraph as cg
def influence(c, ns, supergates=False, approx=True, log_dir=None, **kwargs):
"""
Compute the influences at node(s).
Parameters
----------
c : Circuit
Circuit to compute influence for.
ns : str or list of str
Node(s) to compute influence for.
supergates : bool
If True, break computation into supergates.
approx : bool
Compute approximate model count using approxmc.
log_dir: str or pathlib.Path
Directory to store approxmc logs in.
kwargs: Keyword arguments
Keyword arguments to pass into `approx_model_count`.
Returns
-------
dict of str:float or dict of dict of str:float
The influence each startpoint has on the node. If multiple nodes
are specified, a dict mapping each output to its influences.
"""
if isinstance(ns, str):
ns = [ns]
if supergates:
# Keep track of influences already computed for a given supergate
# Mapping of supergate outputs to dict mapping inputs to influences
sg_influences = {}
all_influences = {}
for n in ns:
sp = c.startpoints(n)
if log_dir:
log_dir = Path(log_dir)
log_dir.mkdir(exist_ok=True)
def mc(circuit, startpoint, endpoints=None):
i = cg.tx.sensitization_transform(circuit, startpoint, endpoints)
if approx:
log_file = None
if log_dir:
log_file = log_dir / f"{s}.approxmc.log"
count = cg.sat.approx_model_count(
i,
{"sat": True},
log_file=log_file,
**kwargs,
)
else:
count = cg.sat.approx_model_count(
i,
{"sat": True},
**kwargs,
)
else:
count = cg.sat.model_count(i, {"sat": True})
return count
influences = {}
if supergates:
# Mapping of circuit inputs to the supergates they belong to
input_map = {}
c_n = cg.tx.subcircuit(c, c.transitive_fanin(n) | {n})
c_n.set_output(c_n.outputs(), False)
c_n.set_output(n)
supergates = cg.tx.supergates(c_n)
for sg in supergates:
# Mapping of supergate inputs to influence on supergate output
(sg_out,) = sg.outputs()
if sg_out not in sg_influences:
curr_influences = {}
for s in sg.startpoints():
input_map[s] = sg_out
curr_influences[s] = mc(sg, s) / (2 ** len(sg.startpoints()))
sg_influences[sg_out] = curr_influences
else:
for s in sg.startpoints():
input_map[s] = sg_out
# Multiply influences along each path
for s in sp:
infl = 1
curr_node = s
while curr_node != n:
sg_out = input_map[curr_node]
infl *= sg_influences[sg_out][curr_node]
curr_node = sg_out
influences[s] = infl
else:
for s in sp:
# create influence circuit
influences[s] = mc(c, s, n) / (2 ** len(sp))
all_influences[n] = influences
if len(all_influences) == 1:
(all_influences,) = all_influences.values()
return all_influences
def avg_sensitivity(c, ns, supergates=False, approx=True, log_dir=None, **kwargs):
"""
Calculate the average sensitivity node(s) `ns`.
Return the average sensitivity (equal to total influence) of node(s) with
respect to startpoints.
Parameters
----------
c: Circuit
Circuit to compute average sensitivity for.
ns : str or list of str
Node(s) to compute average sensitivity for.
supergates: bool
If True, break the sensitivity computation up into supergates.
approx : bool
Compute approximate model count using approxmc.
log_dir: str or pathlib.Path
Directory to store approxmc logs in.
kwargs: Keyword arguments
Keyword arguments to pass into `approx_model_count`.
Returns
-------
float or dict of str:float
Average sensitivity of node `ns` or dict mapping nodes in set `ns`
to average sensitivities.
"""
all_influences = influence(
c, ns, supergates=supergates, approx=approx, log_dir=log_dir, **kwargs
)
if isinstance(ns, str):
return sum(all_influences.values())
total_influences = {}
for k, v in all_influences.items():
total_influences[k] = sum(v.values())
return total_influences
def sensitivity(c, n):
"""
Calculate the sensitivity of node `n` with respect to its startpoints.
Parameters
----------
c: Circuit
Circuit to compute sensitivity for
n : str
Node to compute sensitivity for.
Returns
-------
int
Sensitivity of node n.
"""
sp = c.startpoints(n)
if n in sp:
return 1
sen = len(sp)
s = cg.tx.sensitivity_transform(c, n)
vs = cg.utils.int_to_bin(sen, cg.utils.clog2(len(sp)), True)
while not cg.sat.solve(s, {f"sen_out_{i}": v for i, v in enumerate(vs)}):
sen -= 1
vs = cg.utils.int_to_bin(sen, cg.utils.clog2(len(sp)), True)
return sen
def sensitize(c, n, assumptions=None):
"""
Find an input that sensitizes `n` to an endpoint under assumptions.
Parameters
----------
c: Circuit
Circuit to compute sensitivity for
n : str
Node to compute sensitivity for.
assumptions : dict of str:bool
Assumptions for Circuit.
Returns
-------
dict of str:bool
Input value.
"""
# setup circuit
s = cg.tx.sensitization_transform(c, n)
if not assumptions:
assumptions = {}
# find a sensitizing input
result = cg.sat.solve(s, {"sat": True, **assumptions})
if not result:
return None
return {g: result[g] for g in s.startpoints()}
def signal_probability(c, n, approx=True, **kwargs):
"""
Determine the (approximate) probability of node `n` being true.
Parameters
----------
c : Circuit
Input circuit.
n : str
Node to determine probability for.
approx : bool
Use approximate model counting through approxmc.
This is the default behavior, and turned it off
can make computation time prohibitively expensive.
kwargs: Keyword arguments
Keyword arguments to pass into `approx_model_count`.
Returns
-------
float
Probability.
"""
# get subcircuit ending at node
subc = cg.tx.subcircuit(c, {n} | c.transitive_fanin(n))
# get count with node true and other inputs fixed
if approx:
count = cg.sat.approx_model_count(subc, {n: True}, **kwargs)
else:
count = cg.sat.model_count(subc, {n: True})
return count / (2 ** len(subc.startpoints()))
def levelize(c):
"""
Levelize a circuit.
Compute the logical level of each gate in the circuit.
Parameters
----------
c: Circuit
Input circuit.
Returns
-------
dict of str:int
Mapping of gate names to levels.
"""
if c.is_cyclic():
raise ValueError("Cannot levelize cyclic circuit")
levels = {n: 0 for n in c.inputs() | c.filter_type(("0", "1", "x"))}
for n in c.topo_sort():
if n in levels:
continue
levels[n] = max(levels[fi] for fi in c.fanin(n)) + 1
return levels
Functions
def avg_sensitivity(c, ns, supergates=False, approx=True, log_dir=None, **kwargs)
-
Calculate the average sensitivity node(s)
ns
.Return the average sensitivity (equal to total influence) of node(s) with respect to startpoints.
Parameters
c
:Circuit
- Circuit to compute average sensitivity for.
ns
:str
orlist
ofstr
- Node(s) to compute average sensitivity for.
supergates
:bool
- If True, break the sensitivity computation up into supergates.
approx
:bool
- Compute approximate model count using approxmc.
log_dir
:str
orpathlib.Path
- Directory to store approxmc logs in.
kwargs
:Keyword arguments
- Keyword arguments to pass into
approx_model_count
.
Returns
float
ordict
ofstr:float
- Average sensitivity of node
ns
or dict mapping nodes in setns
to average sensitivities.
Expand source code
def avg_sensitivity(c, ns, supergates=False, approx=True, log_dir=None, **kwargs): """ Calculate the average sensitivity node(s) `ns`. Return the average sensitivity (equal to total influence) of node(s) with respect to startpoints. Parameters ---------- c: Circuit Circuit to compute average sensitivity for. ns : str or list of str Node(s) to compute average sensitivity for. supergates: bool If True, break the sensitivity computation up into supergates. approx : bool Compute approximate model count using approxmc. log_dir: str or pathlib.Path Directory to store approxmc logs in. kwargs: Keyword arguments Keyword arguments to pass into `approx_model_count`. Returns ------- float or dict of str:float Average sensitivity of node `ns` or dict mapping nodes in set `ns` to average sensitivities. """ all_influences = influence( c, ns, supergates=supergates, approx=approx, log_dir=log_dir, **kwargs ) if isinstance(ns, str): return sum(all_influences.values()) total_influences = {} for k, v in all_influences.items(): total_influences[k] = sum(v.values()) return total_influences
def influence(c, ns, supergates=False, approx=True, log_dir=None, **kwargs)
-
Compute the influences at node(s).
Parameters
c
:Circuit
- Circuit to compute influence for.
ns
:str
orlist
ofstr
- Node(s) to compute influence for.
supergates
:bool
- If True, break computation into supergates.
approx
:bool
- Compute approximate model count using approxmc.
log_dir
:str
orpathlib.Path
- Directory to store approxmc logs in.
kwargs
:Keyword arguments
- Keyword arguments to pass into
approx_model_count
.
Returns
dict
ofstr:float
ordict
ofdict
ofstr:float
- The influence each startpoint has on the node. If multiple nodes are specified, a dict mapping each output to its influences.
Expand source code
def influence(c, ns, supergates=False, approx=True, log_dir=None, **kwargs): """ Compute the influences at node(s). Parameters ---------- c : Circuit Circuit to compute influence for. ns : str or list of str Node(s) to compute influence for. supergates : bool If True, break computation into supergates. approx : bool Compute approximate model count using approxmc. log_dir: str or pathlib.Path Directory to store approxmc logs in. kwargs: Keyword arguments Keyword arguments to pass into `approx_model_count`. Returns ------- dict of str:float or dict of dict of str:float The influence each startpoint has on the node. If multiple nodes are specified, a dict mapping each output to its influences. """ if isinstance(ns, str): ns = [ns] if supergates: # Keep track of influences already computed for a given supergate # Mapping of supergate outputs to dict mapping inputs to influences sg_influences = {} all_influences = {} for n in ns: sp = c.startpoints(n) if log_dir: log_dir = Path(log_dir) log_dir.mkdir(exist_ok=True) def mc(circuit, startpoint, endpoints=None): i = cg.tx.sensitization_transform(circuit, startpoint, endpoints) if approx: log_file = None if log_dir: log_file = log_dir / f"{s}.approxmc.log" count = cg.sat.approx_model_count( i, {"sat": True}, log_file=log_file, **kwargs, ) else: count = cg.sat.approx_model_count( i, {"sat": True}, **kwargs, ) else: count = cg.sat.model_count(i, {"sat": True}) return count influences = {} if supergates: # Mapping of circuit inputs to the supergates they belong to input_map = {} c_n = cg.tx.subcircuit(c, c.transitive_fanin(n) | {n}) c_n.set_output(c_n.outputs(), False) c_n.set_output(n) supergates = cg.tx.supergates(c_n) for sg in supergates: # Mapping of supergate inputs to influence on supergate output (sg_out,) = sg.outputs() if sg_out not in sg_influences: curr_influences = {} for s in sg.startpoints(): input_map[s] = sg_out curr_influences[s] = mc(sg, s) / (2 ** len(sg.startpoints())) sg_influences[sg_out] = curr_influences else: for s in sg.startpoints(): input_map[s] = sg_out # Multiply influences along each path for s in sp: infl = 1 curr_node = s while curr_node != n: sg_out = input_map[curr_node] infl *= sg_influences[sg_out][curr_node] curr_node = sg_out influences[s] = infl else: for s in sp: # create influence circuit influences[s] = mc(c, s, n) / (2 ** len(sp)) all_influences[n] = influences if len(all_influences) == 1: (all_influences,) = all_influences.values() return all_influences
def levelize(c)
-
Levelize a circuit.
Compute the logical level of each gate in the circuit.
Parameters
c
:Circuit
- Input circuit.
Returns
dict
ofstr:int
- Mapping of gate names to levels.
Expand source code
def levelize(c): """ Levelize a circuit. Compute the logical level of each gate in the circuit. Parameters ---------- c: Circuit Input circuit. Returns ------- dict of str:int Mapping of gate names to levels. """ if c.is_cyclic(): raise ValueError("Cannot levelize cyclic circuit") levels = {n: 0 for n in c.inputs() | c.filter_type(("0", "1", "x"))} for n in c.topo_sort(): if n in levels: continue levels[n] = max(levels[fi] for fi in c.fanin(n)) + 1 return levels
def sensitivity(c, n)
-
Calculate the sensitivity of node
n
with respect to its startpoints.Parameters
c
:Circuit
- Circuit to compute sensitivity for
n
:str
- Node to compute sensitivity for.
Returns
int
- Sensitivity of node n.
Expand source code
def sensitivity(c, n): """ Calculate the sensitivity of node `n` with respect to its startpoints. Parameters ---------- c: Circuit Circuit to compute sensitivity for n : str Node to compute sensitivity for. Returns ------- int Sensitivity of node n. """ sp = c.startpoints(n) if n in sp: return 1 sen = len(sp) s = cg.tx.sensitivity_transform(c, n) vs = cg.utils.int_to_bin(sen, cg.utils.clog2(len(sp)), True) while not cg.sat.solve(s, {f"sen_out_{i}": v for i, v in enumerate(vs)}): sen -= 1 vs = cg.utils.int_to_bin(sen, cg.utils.clog2(len(sp)), True) return sen
def sensitize(c, n, assumptions=None)
-
Find an input that sensitizes
n
to an endpoint under assumptions.Parameters
c
:Circuit
- Circuit to compute sensitivity for
n
:str
- Node to compute sensitivity for.
assumptions
:dict
ofstr:bool
- Assumptions for Circuit.
Returns
dict
ofstr:bool
- Input value.
Expand source code
def sensitize(c, n, assumptions=None): """ Find an input that sensitizes `n` to an endpoint under assumptions. Parameters ---------- c: Circuit Circuit to compute sensitivity for n : str Node to compute sensitivity for. assumptions : dict of str:bool Assumptions for Circuit. Returns ------- dict of str:bool Input value. """ # setup circuit s = cg.tx.sensitization_transform(c, n) if not assumptions: assumptions = {} # find a sensitizing input result = cg.sat.solve(s, {"sat": True, **assumptions}) if not result: return None return {g: result[g] for g in s.startpoints()}
def signal_probability(c, n, approx=True, **kwargs)
-
Determine the (approximate) probability of node
n
being true.Parameters
c
:Circuit
- Input circuit.
n
:str
- Node to determine probability for.
approx
:bool
- Use approximate model counting through approxmc. This is the default behavior, and turned it off can make computation time prohibitively expensive.
kwargs
:Keyword arguments
- Keyword arguments to pass into
approx_model_count
.
Returns
float
- Probability.
Expand source code
def signal_probability(c, n, approx=True, **kwargs): """ Determine the (approximate) probability of node `n` being true. Parameters ---------- c : Circuit Input circuit. n : str Node to determine probability for. approx : bool Use approximate model counting through approxmc. This is the default behavior, and turned it off can make computation time prohibitively expensive. kwargs: Keyword arguments Keyword arguments to pass into `approx_model_count`. Returns ------- float Probability. """ # get subcircuit ending at node subc = cg.tx.subcircuit(c, {n} | c.transitive_fanin(n)) # get count with node true and other inputs fixed if approx: count = cg.sat.approx_model_count(subc, {n: True}, **kwargs) else: count = cg.sat.model_count(subc, {n: True}) return count / (2 ** len(subc.startpoints()))