Skip to content

Commit

Permalink
update docs (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
nkrusch committed May 2, 2023
1 parent 2d0bc0f commit 4019193
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 55 deletions.
30 changes: 15 additions & 15 deletions docs/examples.md
Expand Up @@ -3,22 +3,22 @@
pymwp analyzes programs written in C language.
The project repository `c_files` directory contains many examples.

## Program Categories

!!! info " "

: :material-hand-wave-outline: &nbsp; **Basics**<br/>Programs performing operations that correspond to simple derivation trees.

: :material-infinity: &nbsp; **Infinite**<br/>Programs that are assigned matrices that always contain infinite coefficients, no matter the choices.

: :octicons-move-to-end-24: &nbsp; **Not Infinite**<br/>Programs that are assigned matrices that do not always contain infinite coefficients.

: :material-circle-outline: &nbsp; **Original Paper**<br/>Examples taken from or inspired by paper "A Flow Calculus of mwp-Bounds for Complexity Analysis".

: :material-asterisk: &nbsp; **Implementation Paper**<br/>Examples from "mwp-Analysis Improvement and Implementation: Realizing Implicit Computational Complexity".

: :material-dots-horizontal: &nbsp; **Other**<br/>Other programs of interest.

### Program Categories

: :material-hand-wave-outline: **Basics**<br/>Programs performing operations that correspond to simple derivation trees.

: :material-infinity: **Infinite**<br/>Programs that are assigned matrices that always contain infinite coefficients, no matter the choices.

: :octicons-move-to-end-24: **Not Infinite**<br/>Programs that are assigned matrices that do not always contain infinite coefficients.

: :material-circle-outline: **Original Paper**<br/>Examples taken from or inspired by paper "A Flow Calculus of mwp-Bounds for Complexity Analysis".

: :material-asterisk: **Implementation Paper**<br/>Examples from paper "mwp-Analysis Improvement and Implementation: Realizing Implicit Computational Complexity".

: :material-dots-horizontal: **Other**<br/>Other programs of interest.

<style>.md-content .md-typeset dd {margin-left:0}</style>

<h3>Demo</h3>

Expand Down
29 changes: 21 additions & 8 deletions docs/utilities.md
Expand Up @@ -4,17 +4,17 @@ There are several utility scripts in the repository `utilities` directory. These
and measure performance of pymwp in different ways. These tools are not shipped with the distributed version of pymwp;
they are only available from the development repository.

!!! info "**Current Utilities**"
## List of Utilities

- **AST Generator** (`ast_util.py`)
: Uses PyCParser to generate an AST of C file(s). It is useful for debugging, testing, and inspecting AST structure.
!!! info " "

: :one: &nbsp; **AST Generator** (`ast_util.py`)<br/>Uses PyCParser to generate an AST of C files. It is useful for debugging, testing, and inspecting AST structure.

- **Results Plotter** (`plot.py`)
: Make plots of analyzer results. This is useful for benchmarking and inspecting performance. This scripts takes as input a directory path to pymwp results (output by default), then generates a table of those results.
: :two: &nbsp; **Results Plotter** (`plot.py`)<br/>Make plots of analyzer results. This is useful for benchmarking and inspecting performance. This scripts takes as input a directory path to pymwp results (output by default), then generates a table of those results.

- **Execution Profiling** (`profiler.py`)
: Profiler inspectes execution of various functions. It is helpful to locate bottlenecks and to understand analyzer function call structure.
: :three: &nbsp; **Execution Profiling** (`profiler.py`)<br/>Profiler inspectes execution of various functions. It is helpful to locate bottlenecks and to understand analyzer function call structure.

: :four: &nbsp; **Machine Details** (`runtime.py`)<br/>Captures details of executing machine &mdash; useful for reporting results of benchmarking or profiling.

## Getting started

Expand Down Expand Up @@ -112,4 +112,17 @@ The results of each execution are stored in corresponding files.
into this category if it does not crash the process.
- error : profiling subprocess terminated in error.
- timeout : profiling subprocess did not terminate within time limit and was forced to quit.


---

## Machine Details

Captures e.g., hardware details for executing machine.

**Usage**

```
python3 utilities/runtime.py [output_dir]
```

Where `output_dir` specifies a directory where to write the machine details.
45 changes: 38 additions & 7 deletions pymwp/bound.py
Expand Up @@ -55,12 +55,34 @@ def __str__(self):

@property
def bound_triple(self) -> Tuple[Tuple[str], Tuple[str], Tuple[str]]:
"""Alternative bounds representation"""
"""Alternative bounds representation.
Example:
```
(X1,) (,) (X4, X5)
```
Returns:
Current bound as $(m_1,...m_n), (w_1,...w_n), (p_1,...p_n)$
where the first contains list of variables in m,
second contains variables in w, and last in p (if any).
"""
return tuple(self.x.vars), tuple(self.y.vars), tuple(self.z.vars)

@property
def bound_triple_str(self) -> str:
"""Alternative bounds representation"""
"""Alternative bounds representation.
Example:
```
X1;;X4,X5
```
Returns:
Current bound as `m;w;p` where the first section contains
list of variables in m, second contains variables in w,
and last in p (if any).
"""
return f'{";".join([",".join(v) for v in self.bound_triple])}'

@staticmethod
Expand Down Expand Up @@ -104,8 +126,15 @@ def __init__(self, bounds: dict = None):
(k, MwpBound(triple=v)) for k, v in bounds.items()]) \
if bounds else {}

def calculate(self, relation: SimpleRelation):
"""Calculate bound from a simple-valued matrix"""
def calculate(self, relation: SimpleRelation) -> Bound:
"""Calculate bound from a simple-valued matrix.
Arguments
relation: a simple-valued relation.
Returns:
The bound for the relation.
"""
vars_, matrix = relation.variables, relation.matrix
for col_id, name in enumerate(vars_):
var_bound = MwpBound()
Expand All @@ -119,12 +148,14 @@ def to_dict(self) -> dict:
return dict([(k, v.bound_triple_str)
for k, v in self.bound_dict.items()])

def show_poly(self, compact=False, significant=False) -> str:
def show_poly(
self, compact: bool = False, significant: bool = False
) -> str:
"""Format a nice display string of bounds.
Arguments:
compact - reduce whitespace in the output
significant - omit bounds that depend only on self
compact: reduce whitespace in the output
significant: omit bounds that depend only on self
Returns:
A formatted string of the bound.
Expand Down
6 changes: 3 additions & 3 deletions pymwp/relation.py
Expand Up @@ -294,8 +294,8 @@ def infty_vars(self) -> Dict[str, List[str]]:
Returns:
Dictionary of potentially infinite dependencies, where
the key is source variable and value is list of targets;
All entries are non-empty.
the key is source variable and value is list of targets.
All entries are non-empty.
"""
vars_ = self.variables
return dict([(x, y) for x, y in [
Expand Down Expand Up @@ -393,7 +393,7 @@ def eval(self, choices: List[int], index: int) -> Choices:

class SimpleRelation(Relation):
"""Specialized instance of relation, where matrix contains only
scalar values, no Polynomials."""
scalar values, no polynomials."""

def __init__(self, variables: Optional[List[str]] = None,
matrix: Optional[List[List[str]]] = None):
Expand Down
102 changes: 80 additions & 22 deletions pymwp/result.py
Expand Up @@ -34,46 +34,77 @@ def dur_ms(self) -> int:
return int(self.time_diff / 1e6)

def on_start(self) -> Timeable:
"""At start of function analysis"""
"""Called at start of timeable entity."""
self.start_time = time.time_ns()
return self

def on_end(self) -> Timeable:
"""Called immediately after function analysis"""
"""Called at end of timeable entity."""
self.end_time = time.time_ns()
return self


class Program(object):
"""Details about analyzed C program."""
"""Details about analyzed C file."""

def __init__(self, n_lines: int = -1, program_path: str = None):
"""Create program object.
Attributes:
n_lines (int): number of lines.
program_path (str): path to program file.
"""
self.n_lines: int = n_lines
self.program_path: Optional[str] = program_path

def to_dict(self):
def to_dict(self) -> Dict[str, Union[int, str]]:
"""Convert Program object to a dictionary."""
return {
'n_lines': self.n_lines,
'program_path': self.program_path}

@property
def name(self) -> Optional[str]:
"""Get name of program, without path and extension.
Returns:
Program name or `None` if not set.
"""
return Path(self.program_path).stem if self.program_path else None

@staticmethod
def from_dict(**kwargs):
def from_dict(**kwargs) -> Program:
"""Initialize Program object from kwargs.
Returns:
Program: initialized program object.
Raises:
KeyError: if all Program attributes are not included as kwargs.
"""
return Program(kwargs['n_lines'], kwargs['program_path'])


class FuncResult(Timeable):
"""Capture details of analysis result for one function."""
"""Capture details of analysis result for one program (function in C)."""

def __init__(
self, name: str, infinite: bool = False,
variables: Optional[List[str]] = None,
relation: Optional[Relation] = None,
choices: Optional[Choices] = None,
bound: Optional[Bound] = None):
"""
Create a function result.
Attributes:
name: function name
infinite: True if result is infinite.
variables: list of variables.
relation: corresponding [`Relation`](relation.md)
choices: choice object [`Choice`](choice.md)
bound: bound object [`Bound`](bound.md)
"""
super().__init__()
self.name = name
self.vars = variables or []
Expand All @@ -84,10 +115,12 @@ def __init__(

@property
def n_vars(self) -> int:
"""Number of variables."""
return len(self.vars)

@property
def n_bounds(self) -> int:
"""Number of bounds."""
return self.choices.n_bounds if self.choices else 0

def to_dict(self) -> dict:
Expand Down Expand Up @@ -126,7 +159,18 @@ def from_dict(**kwargs):


class Result(Timeable):
"""Captures analysis result and details about the process."""
"""Captures analysis result and details about the process.
This result contains
- program: information about analyzed C File:
type [`Program`](result.md#pymwp.result.Program)
- relations: dictionary of function results:
type [`FuncResult`](result.md#pymwp.result.FuncResult)
- analysis time: measured from before any function
has been analyzed, until all functions have been analyzed.
It excludes time to write result to file.
"""

def __init__(self):
super().__init__()
Expand All @@ -135,11 +179,15 @@ def __init__(self):

@property
def n_functions(self) -> int:
"""number of functions in analyzed program"""
"""Number of functions in analyzed program."""
return len(self.relations.keys())

def add_relation(self, func_result: FuncResult) -> None:
"""Appends function analysis outcome to result."""
"""Appends function analysis outcome to result.
Attributes:
func_result: function analysis to append to Result.
"""
self.relations[func_result.name] = func_result
if not func_result.infinite:
if func_result.bound:
Expand All @@ -152,18 +200,20 @@ def add_relation(self, func_result: FuncResult) -> None:
logger.info('Possibly problematic flows:')
logger.info(func_result.relation.infty_pairs())

def get_func(self, name: Optional[str] = None) \
-> Union[FuncResult, Dict[str, FuncResult]]:
"""Returns the analysis result.
- If `name` argument is provided and key exists, returns value match.
- If program contained 1 function, returns result for that function.
def get_func(
self, name: Optional[str] = None
) -> Union[FuncResult, Dict[str, FuncResult]]:
"""Returns analysis result for function(s).
Otherwise, returns a dictionary of results for each
analyzed function, as in: `<function_name, analysis_result>`
* If `name` argument is provided and key exists,
returns function result for exact value match.
* If program contained exactly 1 function,
returns result for that function.
* Otherwise, returns a dictionary of results for each
analyzed function, as in: `<function_name, analysis_result>`
Arguments:
name - name of function
name: name of function
Returns:
Function analysis result, or dictionary of results.
Expand All @@ -175,7 +225,7 @@ def get_func(self, name: Optional[str] = None) \
return self.relations[key]
return self.relations

def log_result(self):
def log_result(self) -> None:
"""Display here all interesting stats."""
if self.n_functions == 0:
logger.warning("Input C file contained no analyzable functions!")
Expand All @@ -184,8 +234,12 @@ def log_result(self):
dur_ms = int(self.time_diff / 1e6)
logger.info(f'Total time: {dur_sc} s ({dur_ms} ms)')

def serialize(self):
"""JSON serialize result object."""
def serialize(self) -> dict:
"""JSON serialize a result object.
Returns:
dict: dictionary representation.
"""
return {
'start_time': self.start_time,
'end_time': self.end_time,
Expand All @@ -194,7 +248,11 @@ def serialize(self):

@staticmethod
def deserialize(**kwargs) -> Result:
"""Reverse of serialize."""
"""Reverse of serialize.
Returns:
Result: Initialized Result object.
"""
st, et, r = 'start_time', 'end_time', Result()
r.start_time = int(kwargs[st]) if st in kwargs else 0
r.end_time = int(kwargs[et]) if et in kwargs else 0
Expand Down

0 comments on commit 4019193

Please sign in to comment.