diff --git a/eqn_lib.py b/Examples/eqn_lib.py similarity index 100% rename from eqn_lib.py rename to Examples/eqn_lib.py diff --git a/ladder_problem-Copy1.png b/Examples/ladder_problem-Copy1.png similarity index 100% rename from ladder_problem-Copy1.png rename to Examples/ladder_problem-Copy1.png diff --git a/README.md b/README.md index 23bfea3..9657dda 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,8 @@ a &= \frac{ 2 }{ 3 } \cdot \sqrt{ \pi } = \frac{ 2 }{ 3 } \cdot \sqrt{ 3.142 } & ``` ## Basic Usage 2: As a decorator on your functions, `@handcalc()` -This is the same kind of thing except instead of running your code in a Jupyter cell, you are running your code in a Python function, which is treated like a Jupyter cell. +_Shout-out to @eriknw for developing [innerscope](https://github.com/eriknw/innerscope) and proactively integrating it into `handcalcs`. Thank you!_ + Start by importing the `@handcalc()` decorator: @@ -111,20 +112,14 @@ Start by importing the `@handcalc()` decorator: from handcalcs.decorator import handcalc ``` -Then, write your function. - ```python -@handcalc() -def my_calc(x, y, z): - a = 2*x / y - b = 3*a - c = (a + b) / z +@handcalc([override: str = "", precision: int = 3, left: str = "", right: str = "", jupyter_display: bool = False]) ``` -#### `@handcalc(left: str = "", right: str = "", jupyter_display: bool = False)` - Returns a tuple consisting of `(latex_code: str, locals: dict)`, where `locals` is a dictionary of all variables in the scope of the function namespace. +* `override` is a str representing one of the acceptable override tags (see below) +* `precision` is an int to alter the of decimal precision displayed * `left` and `right` are strings that can precede and follow the encoded Latex string, such as `\\[` and `\\]` or `$` and `$` * `jupyter_display`, when True, will return only the `locals` dictionary and instead will display the encoded Latex string rendering with `display(Latex(latex_code))` from `IPython.display`. Will return an error if not used within @@ -132,28 +127,52 @@ In your decorated function, everything between `def my_calc(...)` and a return s Used in this way, you can use `@handcalc()` to dynamically generate Latex code for display in Jupyter and non-Jupypter Python environments (e.g. streamlit). +![Parameters](docs/images/decorator.png) + + +## Override tags + +`handcalcs` makes certain assumptions about how you would like your calculation formatted and does not allow for a great deal of customization in this regard. However, there are currently **four** customizations you can make using `# override tags` as an argument after the `%%render` cell magic. Additionally, you can also specify the number of decimals of precision to display. You can only use __one__ override tag per cell **but** you can combine an override tag with a precision setting. + +**Override tags can be used with both the Jupyter cell magic and the function decorator**. To use a override tag with the decorator, you just supply it as an argument, e.g. `@handcalc(override='params', precision=2)` -## Comment Tags +I will compare a basic rendering of the quadratic formula (below) with the change achieved with each override tag. -`handcalcs` makes certain assumptions about how you would like your calculation formatted and does not allow for a great deal of customization in this regard. However, there are currently **three** customizations you can make using `# comment tags` as the _first line of your cell_ after the `%%render` cell magic. You can only use __one__ comment tag per cell. +### Basic rendering: +![Parameters](docs/images/quadratic_formula_basic.png) -**Comment tags can be used with both the Jupyter cell magic and the function decorator**. To use a comment tag with the decorator, the comment tag must be the first line after the signature (i.e. the `def func_name():`) -### `# Parameters`: +___ + +### `params`: `handcalcs` renders lines of code vertically, one after the other. However, when you are assigning variables, or displaying resulting variables, you may not want to waste all of that vertical space. -Using the `# Parameters` comment tag, your list of parameters will instead render in three columns, thereby saving vertical space. +Using the `params` override tag, your list of parameters will instead render in three columns, thereby saving vertical space. Additionally, onsly the result will be shown, no calculations. + +![Params override example](docs/images/quadratic_formula_params.png) + +___ -![Parameters](docs/images/parameters.gif) +### Adjust precision: +The number of decimal places in a cell can be adjusted by providing an integer after `%%render` to indicate the decimal precision to be displayed. Can be combined with another override tag. -### `# Long` and `# Short`: +![Precision override example](docs/images/quadratic_formula_precision.png) + +___ +### `long` and `short`: To save vertical space, `handcalcs` _attempts_ to figure out how long your calculation is and, if it is short enough, renders it out fully on one line. If `handcalcs`'s internal test deems the calculation as being too long to fit onto one line, it breaks it out into multiple lines. -Use the `# Long` or `# Short` comment tags to override the length check and display the calculation in the "Long" format or the "Short" format for all calculations in the cell. e.g. +Use the `# long` or `# short` override tags to override the length check and display the calculation in the "Long" format or the "Short" format for all calculations in the cell. e.g. + +#### `long: Spans multiple lines as though you had a long equation` + +![Long override example](docs/images/quadratic_formula_long.png) + +#### `short: Forced to a single line as though you had a short equation` ```python # Format for "short" calculations (can fit on one line): c = 2*a + b/3 = 2*(2) + (3)/3 = 5 @@ -163,20 +182,31 @@ Use the `# Long` or `# Short` comment tags to override the length check and disp = 2*(2) + (3)/3 = 5 ``` +![Short override example](docs/images/quadratic_formula_short.png) -![Short and Long calculations](docs/images/shortandlong.gif) - - -### `# Symbolic` +___ +### `symbolic` The primary purpose of `handcalcs` is to render the full calculation with the numeric substitution. This allows for easy traceability and verification of the calculation. -However, there may be instances when it is preferred to simply display calculations symbolically. For example, you can use the `# Symbolic` tag to use `handcalcs` as a fast way to render Latex equations symbolically. +However, there may be instances when it is preferred to simply display calculations symbolically. For example, you can use the `symbolic` tag to use `handcalcs` as a fast way to render Latex equations symbolically. Alternatively, you may prefer to render out all of input parameters in one cell, your formulae symbolically in the following cell, and then all the final values in the last cell, skipping the numeric substitution process entirely. -Keep in mind that even if you use the `# Symbolic` tag with your calculations, you still need to declare those variables (by assigning values to them) ahead of time in order for your calculation to be valid Python. +Keep in mind that even if you use the `symbolic` tag with your calculations, you still need to declare those variables (by assigning values to them) ahead of time in order for your calculation to be valid Python. + +![Short override example](docs/images/quadratic_formula_symbolic.png) + +--- + +### `sympy` + +This is intended to be used only with `sympy` loaded. Sympy allows for symbolic manipulation, solving, and integration of algebraic expressions. Sympy will render its own objects in Latex without handcalcs. + +If you are manipulating a sympy expression or sympy equation for the purpose of calculation, you can use `handcalcs` to handle the substitution and calculation of your resulting expression.
-![handcalcs with forallpeople](docs/images/symbolic.gif) +_Note: Re-assigning your symbolic variables to numbers will clobber them as sympy variables. However, you are done with these now, right? So, it's no problem. If you need to work symbolically again, just re-run your notebook cells from the top._ + +![Sympy demo](docs/images/sympy.png) --- @@ -184,7 +214,6 @@ Keep in mind that even if you use the `# Symbolic` tag with your calculations, y `handcalcs` was designed to be used with the units package, [forallpeople](https://github.com/connorferster/forallpeople) (and [forallpeople](https://github.com/connorferster/forallpeople) was designed to be compatible with `handcalcs`). However, it has been recently reported that [pint](pint.readthedocs.org) can work to good effect, also. -![handcalcs with forallpeople](docs/images/forallpeople.gif) **For potential compatibility with other units packages, please see [the wiki.](https://github.com/connorferster/handcalcs/wiki)** @@ -194,9 +223,9 @@ Keep in mind that even if you use the `# Symbolic` tag with your calculations, y ## Features ### Quickly display the values of many variables -In Python, displaying the value of many variables often requires tediously typing them all into a series of `print()` statements. `handcalcs` makes this much easier: +No more `print` statements needed. Just plop your variables onto a line and they will all be displayed. -![display variable demo](docs/images/outputs.gif) +![display variable demo](docs/images/display_var.png) ### Get Just the Latex Code, without the render If you just want to generate the rendered Latex code directly to use in your own Latex files, you can use the `%%tex` cell magic instead: @@ -210,7 +239,7 @@ c = 2*a + b/3 Then you can just copy and paste the result into your own LaTeX document. -![tex cell magic demo](docs/images/tex.gif) +![tex cell magic demo](docs/images/tex.png) --- @@ -218,7 +247,7 @@ Then you can just copy and paste the result into your own LaTeX document. Subscripts in variable names are automatically created when `_` is used in the variable name. Sub-subscripts are nested for each separate `_` used in series. -![Subscripts demo](docs/images/subscripts.gif) +![Subscripts demo](docs/images/subscripts.png) ---- @@ -231,7 +260,7 @@ Any variable name that contains a Greek letter (e.g. "pi", "upsilon", "eta", etc * Using a Capitalized Name for your variable will render it as an upper case Greek letter. -![Greek symbols demo](docs/images/greeks.gif) +![Greek symbols demo](docs/images/greeks.png) --- @@ -243,7 +272,7 @@ If you are creating your own functions, then they will be rendered in Latex as a If you are using a function with the name `sqrt` (whether your own custom implementation or from `math.sqrt`), then it will be rendered as the radical sign. -![Functions](docs/images/funcs.gif) +![Functions](docs/images/greek.png) --- @@ -253,7 +282,7 @@ Any comments placed after a line of calculation will be rendered as an inline co This makes it convenient to make notes along side your calculations to briefly explain where you may have acquired or derived a particular value. -![Comments](docs/images/comments.gif) +![Comments](docs/images/comments.png) --- @@ -263,7 +292,7 @@ Any calculation entirely wrapped in parentheses, `()`, will be rendered as just This can be convient when you want to calculate a parameter on the fly and not have it be the focus of the calculation. -![Skip the substitution](docs/images/skip_subs.gif) +![Skip the substitution](docs/images/no_subs.png) --- @@ -273,7 +302,7 @@ Many calculations in the "real world" are dependent on context. `handcalcs` allows for the inclusion of some simple conditional statements into its code in a way that makes it easier to understand the context of the calculation. -![Conditional calculations](docs/images/conditionals.gif) +![Conditional calculations](docs/images/conditionals.png) *Note: Multiple "lines" of calculations can be used after the conditional expression provided that they are all on the same line and separated with "`;`". See [Expected Behaviours](https://github.com/connorferster/handcalcs#expected-behaviours) for more context.* @@ -285,12 +314,22 @@ You can use `scipy.quad` to perform numeric integration on a pre-defined functio This behaviour is triggered if you use a function with either `integrate` or `quad` in the name. -![Numeric integration](docs/images/integration.gif) +![Numeric integration](docs/images/integration.png) + +--- + +### "Prime" notation + +Sometimes you need to write "prime" on your variables: + +![Prime Notation](docs/images/prime.png) --- ## PDF Printing in Jupyter +_Note: With `nbconvert` v6.0, templates are handled in a different manner that is incompatible with the below method. Be sure to use `nbconvert` v5.6.1 to allow template installation and swapping._ + Jupyter Notebooks/Lab are able to print notebooks to PDF through two methods. Both can produce great results with handcalcs: 1. **Export to HTML**: Open the exported HTML page in your browser and print to PDF using your system's own PDF printer @@ -302,9 +341,15 @@ Jupyter Notebooks/Lab are able to print notebooks to PDF through two methods. Bo PDF notebooks made with handcalcs tend to look better if the code input cells are suppressed. To make this convenient, handcalcs ships with two modified nbconvert template files that can be installed by running a function in Jupyter before exporting. -### `handcalcs.install_templates.install_html(swap_in:str = "", swap_out:str = "full.tpl", restore:bool = False)` +```python +handcalcs.install_templates.install_html(swap_in:str = "", swap_out:str = "full.tpl", restore:bool = False) + +``` + +```python +handcalcs.install_templates.install_latex(swap_in:str = "", swap_out:str = "article.tplx", restore:bool = False) -### `handcalcs.install_templates.install_latex(swap_in:str = "", swap_out:str = "article.tplx", restore:bool = False)` +``` **`swap_in`**: the name of the handcalcs template file you wish to install. When not provided, the function will print a list of available templates whose names are acceptable inputs for this argument.
**`swap_out`**: the name of the nbconvert template file you wish to replace (default file is nbconvert's default html or latex template, respectively)
@@ -345,7 +390,7 @@ Available templates: >>> install_html(restore = True) # To revert this change to your template files /user/Name/path/to/your/nbconvert/templates/dir/html/full.tpl --was deleted, and replaced with- +-was restored from- /user/Name/path/to/your/nbconvert/templates/dir/html/full_swapped.tpl ``` --- @@ -360,7 +405,7 @@ Given that, handcalcs only renders a small subset of Python and there is a lot t ### Accepted datatypes -`handcalcs` will make an attempt to render all datatypes. However it is not intended for "collections"-based types (e.g. `list`, `tuple`, `dict`, etc.) +`handcalcs` will make an attempt to render all datatypes. However, it cannot yet render all "collection" based data types, e.g. `list` and `dict`. If you are using a collection to hold argument functions, e.g. `sum((23, 123, 45))`, use a `tuple` to ensure it is rendered properly. Alternatively, you can use one-dimensional `numpy` arrays (vectors) with handcalcs. Objects are rendered into Latex by two main approaches: @@ -384,37 +429,6 @@ If you are using object types which have str methods that render as ` str: +def latex( + raw_python_source: str, calculated_results: dict, override: str, precision: int = 3 +) -> str: """ Returns the Python source as a string that has been converted into latex code. """ @@ -223,7 +225,9 @@ def latex(raw_python_source: str, calculated_results: dict, override: str, preci return cell.latex_code -def create_param_cell(raw_source: str, calculated_result: dict, precision: int) -> ParameterCell: +def create_param_cell( + raw_source: str, calculated_result: dict, precision: int +) -> ParameterCell: """ Returns a ParameterCell. """ @@ -239,7 +243,9 @@ def create_param_cell(raw_source: str, calculated_result: dict, precision: int) return cell -def create_long_cell(raw_source: str, calculated_result: dict, precision: int) -> LongCalcCell: +def create_long_cell( + raw_source: str, calculated_result: dict, precision: int +) -> LongCalcCell: """ Returns a LongCalcCell. """ @@ -254,7 +260,9 @@ def create_long_cell(raw_source: str, calculated_result: dict, precision: int) - return cell -def create_short_cell(raw_source: str, calculated_result: dict, precision: int) -> ShortCalcCell: +def create_short_cell( + raw_source: str, calculated_result: dict, precision: int +) -> ShortCalcCell: """ Returns a ShortCell """ @@ -269,7 +277,9 @@ def create_short_cell(raw_source: str, calculated_result: dict, precision: int) return cell -def create_symbolic_cell(raw_source: str, calculated_result: dict, precision: int) -> SymbolicCell: +def create_symbolic_cell( + raw_source: str, calculated_result: dict, precision: int +) -> SymbolicCell: """ Returns a SymbolicCell """ @@ -284,17 +294,19 @@ def create_symbolic_cell(raw_source: str, calculated_result: dict, precision: in return cell -def create_calc_cell(raw_source: str, calculated_result: dict, precision: int) -> CalcCell: +def create_calc_cell( + raw_source: str, calculated_result: dict, precision: int +) -> CalcCell: """ Returns a CalcCell """ cell = CalcCell( - source=raw_source, - calculated_results=calculated_result, - precision=precision, - lines=deque([]), - latex_code="", - ) + source=raw_source, + calculated_results=calculated_result, + precision=precision, + lines=deque([]), + latex_code="", + ) return cell @@ -412,13 +424,17 @@ def categorize_line( split_parameter_line(line, calculated_results), comment, "" ) else: - categorized_line = LongCalcLine(expr_parser(line), comment, "") # code_reader + categorized_line = LongCalcLine( + expr_parser(line), comment, "" + ) # code_reader return categorized_line elif override == "symbolic": - categorized_line = SymbolicLine(expr_parser(line), comment, "") # code_reader + categorized_line = SymbolicLine( + expr_parser(line), comment, "" + ) # code_reader return categorized_line elif override == "short": - categorized_line = CalcLine(expr_parser(line), comment, "") # code_reader + categorized_line = CalcLine(expr_parser(line), comment, "") # code_reader return categorized_line elif True: pass # Future override conditions to match new cell types can be put here @@ -453,7 +469,7 @@ def categorize_line( ) elif "=" in line: - categorized_line = CalcLine(expr_parser(line), comment, "") # code_reader + categorized_line = CalcLine(expr_parser(line), comment, "") # code_reader elif len(expr_parser(line)) == 1: categorized_line = ParameterLine( @@ -1170,18 +1186,20 @@ def test_for_parameter_line(line: str) -> bool: parameter (e.g. "a = 34") instead of an actual calculation. """ # Fast Tests - if not line.strip(): # Blank lines + if not line.strip(): # Blank lines return False - elif len(line.strip().split()) == 1: # Outputing variable names + elif len(line.strip().split()) == 1: # Outputing variable names return True - elif "=" not in line or "if " in line or ":" in line: # conditional lines + elif "=" not in line or "if " in line or ":" in line: # conditional lines return False # Exploratory Tests _, right_side = line.split("=", 1) right_side = right_side.replace(" ", "") - right_side_deque = expr_parser(right_side) - if (right_side.find("(") == 0) and (right_side.find(")") == len(right_side) - 1): # Blocked by parentheses + right_side_deque = expr_parser(right_side) + if (right_side.find("(") == 0) and ( + right_side.find(")") == len(right_side) - 1 + ): # Blocked by parentheses return True elif len(right_side_deque) == 1: return True @@ -1191,7 +1209,6 @@ def test_for_parameter_line(line: str) -> bool: return False - def test_for_parameter_cell(raw_python_source: str) -> bool: """ Returns True if the text, "# Parameters" or "#Parameters" is the line @@ -1275,7 +1292,7 @@ def test_for_scientific_notation_str(elem: str) -> bool: test_for_float = True except: pass - + if "e" in str(elem).lower() and test_for_float: return True return False @@ -1471,11 +1488,9 @@ def check_prev_cond_type(self, cond_type: str) -> bool: def swap_calculation(calculation: deque, calc_results: dict) -> tuple: """Returns the python code elements in the deque converted into latex code elements in the deque""" - #calc_w_integrals_preswapped = swap_integrals(calculation, calc_results) + # calc_w_integrals_preswapped = swap_integrals(calculation, calc_results) symbolic_portion = swap_symbolic_calcs(calculation, calc_results) - calc_drop_decl = deque( - list(calculation)[1:] - ) # Drop the variable declaration + calc_drop_decl = deque(list(calculation)[1:]) # Drop the variable declaration numeric_portion = swap_numeric_calcs(calc_drop_decl, calc_results) return (symbolic_portion, numeric_portion) @@ -1488,17 +1503,17 @@ def swap_symbolic_calcs(calculation: deque, calc_results: dict) -> deque: swap_math_funcs, swap_frac_divs, swap_py_operators, + swap_superscripts, swap_comparison_ops, swap_for_greek, swap_scientific_notation_str, - swap_prime_notation, # Fix problem here + swap_prime_notation, # Fix problem here swap_long_var_strs, extend_subscripts, swap_superscripts, flatten_deque, ] for function in functions_on_symbolic_expressions: - if function is swap_math_funcs: symbolic_expression = function(symbolic_expression, calc_results) else: @@ -1526,17 +1541,13 @@ def swap_numeric_calcs(calculation: deque, calc_results: dict) -> deque: return numeric_expression - def swap_integrals(d: deque, calc_results: dict) -> deque: """ Returns 'calculation' with any function named "quad" or "integrate" rendered as an integral. """ swapped_deque = deque([]) - if ( - "integrate" == d[0] - or "quad" == d[0] - ): + if "integrate" == d[0] or "quad" == d[0]: args_deque = d[1] function_name = args_deque[0] function = calc_results.get(function_name, function_name) @@ -1561,13 +1572,13 @@ def swap_integrals(d: deque, calc_results: dict) -> deque: return d - def flatten_deque(d: deque) -> deque: new_deque = deque([]) for item in flatten(d): new_deque.append(item) return new_deque + def flatten(items: Any, omit_parentheses: bool = False) -> deque: """Returns elements from a deque and flattens elements from sub-deques. Inserts latex parentheses ( '\\left(' and '\\right)' ) where sub-deques @@ -1730,7 +1741,7 @@ def swap_frac_divs(code: deque) -> deque: swapped_deque.append(swap_frac_divs(item)) else: swapped_deque.append(item) - new_item = f"{b}"*close_bracket_token + new_item = f"{b}" * close_bracket_token close_bracket_token = 0 swapped_deque.append(new_item) elif isinstance(item, deque): @@ -1741,39 +1752,6 @@ def swap_frac_divs(code: deque) -> deque: return swapped_deque - -# def swap_math_funcs(pycode_as_deque: deque) -> deque: -# """ -# Returns a deque representing 'pycode_as_deque' but with appropriate -# parentheses inserted. -# """ -# a = "{" -# b = "}" -# swapped_deque = deque([]) -# for item in pycode_as_deque: -# if isinstance(item, deque): -# possible_func = not test_for_typ_arithmetic(item) -# poss_func_name = get_function_name(item) -# func_name_match = get_func_latex(poss_func_name) -# if possible_func and (poss_func_name != func_name_match): -# item = swap_func_name(item, poss_func_name) -# item = insert_func_braces(item) -# new_item = swap_math_funcs(item) -# swapped_deque.append(new_item) -# elif possible_func and poss_func_name == func_name_match: -# ops = "\\operatorname" -# new_func = f"{ops}{a}{poss_func_name}{b}" -# item = swap_func_name(item, poss_func_name, new_func) -# item = insert_func_braces(item) -# new_item = swap_math_funcs(item) -# swapped_deque.append(new_item) -# else: -# swapped_deque.append(item) -# else: -# swapped_deque.append(item) -# return swapped_deque - - def swap_math_funcs(pycode_as_deque: deque, calc_results: dict) -> deque: """ Returns a deque representing 'pycode_as_deque' but with appropriate @@ -1813,6 +1791,7 @@ def swap_math_funcs(pycode_as_deque: deque, calc_results: dict) -> deque: swapped_deque.append(item) return swapped_deque + def get_func_latex(func: str) -> str: """ Returns the Latex equivalent of the function name, 'func'. @@ -1835,7 +1814,7 @@ def get_func_latex(func: str) -> str: "asinh": "\\arcsinh", "acosh": "\\arccosh", "atanh": "\\arctanh", - "sum": "\\Sigma" + "sum": "\\Sigma", } return latex_math_funcs.get(func, func) @@ -1853,16 +1832,16 @@ def insert_func_braces(d: deque) -> deque: d_len = len(d) last_idx = d_len - 1 for idx, elem in enumerate(d): - if last_idx == 1: # Special case, func is sqrt or other non-parenth func + if last_idx == 1: # Special case, func is sqrt or other non-parenth func swapped_deque.append(d[0]) swapped_deque.append(a) swapped_deque.append(d[1]) swapped_deque.append(b) return swapped_deque - elif idx == 1: # func name is 0, brace at 1 + elif idx == 1: # func name is 0, brace at 1 swapped_deque.append(a) swapped_deque.append(elem) - elif idx == last_idx: # brace at end + elif idx == last_idx: # brace at end swapped_deque.append(elem) swapped_deque.append(b) else: @@ -1882,7 +1861,7 @@ def swap_func_name(d: deque, old: str, new: str = "") -> deque: else: swapped_func = get_func_latex(elem) swapped_deque.append(swapped_func) - else: + else: swapped_deque.append(elem) return swapped_deque @@ -1955,6 +1934,7 @@ def swap_scientific_notation_float(line: deque, precision: int) -> deque: swapped_deque.append(item) return swapped_deque + def swap_comparison_ops(pycode_as_deque: deque) -> deque: """ Returns a deque representing 'pycode_as_deque' with any python @@ -2063,7 +2043,7 @@ def test_for_long_var_strs(elem: Any) -> bool: return False if "\\" in elem or "{" in elem or "}" in elem: return False - components = elem.replace("'","").split("_") + components = elem.replace("'", "").split("_") if len(components) != 1: top_level, *_remainders = components if len(top_level) == 1: @@ -2144,12 +2124,15 @@ def test_for_function_special_case(d: deque) -> bool: Returns True if 'd' qualifies for a typical function that should have some form of function brackets around it. """ - if len(d) == 2 and\ - (isinstance(d[0], str) and re.match(r"^[A-Za-z0-9_]+$", d[0])) and\ - (isinstance(d[1], str) and re.match(r"^[A-Za-z0-9_]+$", d[1])): + if ( + len(d) == 2 + and (isinstance(d[0], str) and re.match(r"^[A-Za-z0-9_]+$", d[0])) + and (isinstance(d[1], str) and re.match(r"^[A-Za-z0-9_]+$", d[1])) + ): return True - elif (isinstance(d[0], str) and re.match(r"^[A-Za-z0-9_]+$", d[0])) and\ - isinstance(d[1], deque): + elif (isinstance(d[0], str) and re.match(r"^[A-Za-z0-9_]+$", d[0])) and isinstance( + d[1], deque + ): return True else: return False @@ -2161,7 +2144,8 @@ def test_for_unary(d: deque) -> bool: False otherwise. """ ops = "+ -".split() - if len(d) == 2 and d[0] in ops: return True + if len(d) == 2 and d[0] in ops: + return True return False @@ -2182,7 +2166,7 @@ def test_for_typ_arithmetic(d: deque) -> bool: # is_digit = False # lpar = False # is_op = False - + # # Set bool vars # if isinstance(d[0], str): # is_str = True @@ -2193,10 +2177,10 @@ def test_for_typ_arithmetic(d: deque) -> bool: # is_deque = isinstance(d[0], deque) # if isinstance(d[1], str): # is_op = d[1] in operators - + # # Do the test for typ arithmetic - # if (len(d) >= 3 - # and ((is_str and (poss_var_func or is_digit or lpar)) + # if (len(d) >= 3 + # and ((is_str and (poss_var_func or is_digit or lpar)) # or (is_deque and is_op))): # return True # return False @@ -2207,10 +2191,10 @@ def get_function_name(d: deque) -> str: Returns the function name if 'd' represents a deque containing a function name (both typical case and special case). """ - if test_for_function_special_case(d): return d[0] - elif ( - (isinstance(d[0], str) and d[0].isidentifier()) - and (isinstance(d[1], deque) or d[1] == "\\left(") + if test_for_function_special_case(d): + return d[0] + elif (isinstance(d[0], str) and d[0].isidentifier()) and ( + isinstance(d[1], deque) or d[1] == "\\left(" ): return d[0] else: @@ -2232,7 +2216,6 @@ def insert_unary_parentheses(d: deque) -> deque: return swapped_deque - def test_for_fraction_exception(item: Any, next_item: Any) -> bool: """ Returns True if a combination 'item' and 'next_item' appear to indicate @@ -2242,8 +2225,10 @@ def test_for_fraction_exception(item: Any, next_item: Any) -> bool: item="/", next_item=deque -> True False otherwise """ - if isinstance(item, deque) and next_item == "/": return True - elif item == "/" and isinstance(next_item, deque): return True + if isinstance(item, deque) and next_item == "/": + return True + elif item == "/" and isinstance(next_item, deque): + return True return False @@ -2282,11 +2267,14 @@ def insert_arithmetic_parentheses(d: deque) -> deque: rpar = "\\right)" swapped_deque = deque([]) last = len(d) - 1 + exp_check = False + if last > 1: + exp_check = d[1] == "**" # Don't double up parenth on exponents for idx, item in enumerate(d): - if idx == 0: + if idx == 0 and not exp_check: swapped_deque.append(lpar) swapped_deque.append(item) - elif idx == last: + elif idx == last and not exp_check: swapped_deque.append(item) swapped_deque.append(rpar) else: @@ -2306,38 +2294,42 @@ def insert_parentheses(pycode_as_deque: deque) -> deque: func_exclude = ["sqrt", "quad", "integrate", "log", "ceil", "floor"] skip_fraction_token = False for item in peekable_deque: + # breakpoint() next_item = peekable_deque.peek(False) if isinstance(item, deque): poss_func_name = get_function_name(item) if poss_func_name: - if test_for_fraction_exception(item, next_item): + if test_for_fraction_exception(item, next_item): skip_fraction_token = True if not poss_func_name in func_exclude: item = insert_function_parentheses(item) new_item = insert_parentheses(item) swapped_deque.append(new_item) - elif (test_for_typ_arithmetic(item) - and not prev_item == lpar - and not skip_fraction_token - ): + elif ( + test_for_typ_arithmetic(item) + and not prev_item == lpar + and not skip_fraction_token + ): if test_for_fraction_exception(item, next_item): skip_fraction_token = True new_item = insert_parentheses(item) swapped_deque.append(new_item) else: - if prev_item not in func_exclude and not test_for_nested_deque(item): + if prev_item not in func_exclude and not test_for_nested_deque( + item + ): item = insert_arithmetic_parentheses(item) new_item = insert_parentheses(item) swapped_deque.append(new_item) - - elif (test_for_unary(item)): + + elif test_for_unary(item): item = insert_unary_parentheses(item) new_item = insert_parentheses(item) swapped_deque.append(new_item) else: if skip_fraction_token and prev_item == "/": - skip_fraction_token = False + skip_fraction_token = False new_item = insert_parentheses(item) swapped_deque.append(new_item) else: @@ -2349,9 +2341,10 @@ def insert_parentheses(pycode_as_deque: deque) -> deque: prev_item = item return swapped_deque + def test_for_nested_deque(d: deque) -> bool: """ Returns true if 'd' has a deque as its first item. False otherwise """ - return next(isinstance(i, deque) for i in d) \ No newline at end of file + return next(isinstance(i, deque) for i in d) diff --git a/handcalcs/render.py b/handcalcs/render.py index ab24d39..73966ab 100644 --- a/handcalcs/render.py +++ b/handcalcs/render.py @@ -17,10 +17,9 @@ from . import handcalcs as hand from . import sympy_kit as s_kit -try: - from IPython.core.magic import (Magics, magics_class, cell_magic, register_cell_magic) +try: + from IPython.core.magic import Magics, magics_class, cell_magic, register_cell_magic from IPython import get_ipython -# from IPython.core.magics.namespace import NamespaceMagics from IPython.display import Latex, Markdown, display from IPython.utils.capture import capture_output except ImportError: @@ -31,8 +30,11 @@ ip = get_ipython() cell_capture = capture_output(stdout=True, stderr=True, display=True) except AttributeError: - raise ImportError("handcalcs.render is intended for a Jupyter environment." - " Use 'from handcalcs import handcalc' for the decorator interface.") + raise ImportError( + "handcalcs.render is intended for a Jupyter environment." + " Use 'from handcalcs import handcalc' for the decorator interface." + ) + def parse_line_args(line: str) -> dict: """ @@ -45,7 +47,7 @@ def parse_line_args(line: str) -> dict: precision = "" for arg in line_parts: for valid_arg in valid_args: - if arg.lower() in valid_arg: + if arg.lower() in valid_arg: parsed_args.update({"override": valid_arg}) break try: @@ -65,10 +67,13 @@ def render(line, cell): if line_args["override"] == "sympy": cell = s_kit.convert_sympy_cell_to_py_cell(cell, user_ns_prerun) - + # Run the cell with cell_capture: - ip.run_cell(cell) + exec_result = ip.run_cell(cell) + + if not exec_result.success: + return None # Retrieve updated variables (after .run_cell(cell)) user_ns_postrun = ip.user_ns @@ -92,7 +97,7 @@ def tex(line, cell): if line_args["override"] == "sympy": cell = s_kit.convert_sympy_cell_to_py_cell(cell, user_ns_prerun) - + # Run the cell with cell_capture: ip.run_cell(cell) @@ -110,13 +115,15 @@ def tex(line, cell): if line_args["override"] == "_testing": return latex_code + def load_ipython_extension(ipython): """This function is called when the extension is loaded. It accepts an IPython InteractiveShell instance. We can register the magic with the `register_magic_function` method of the shell instance.""" - ipython.register_magic_function(render, 'cell') + ipython.register_magic_function(render, "cell") + # def unload_ipython_extension(ipython): # """This function is called when the extension is diff --git a/pyproject.toml b/pyproject.toml index 6184b91..fc543dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ nbconvert = "^5.6.1" pdbpp = "^0.10.2" ipython = "^7.18.1" sympy = "^1.6.2" +forallpeople = "^2.0.1" [build-system] requires = ["poetry>=0.12"] diff --git a/test_handcalcs/test_handcalcs_file.py b/test_handcalcs/test_handcalcs_file.py index 5b36bfa..5c3caaa 100644 --- a/test_handcalcs/test_handcalcs_file.py +++ b/test_handcalcs/test_handcalcs_file.py @@ -45,6 +45,7 @@ # with pytest.raises(ImportError): # import handcalcs.render + def remove_imports_defs_and_globals(source: str): """ For "cell" modules used in testing, this function removes @@ -74,12 +75,14 @@ def remove_imports_defs_and_globals(source: str): acc.append(line) return "\n".join(acc) + @handcalc() def func_1(x, y): a = 2 * x b = 3 * a + y return b + @handcalc(jupyter_display=True) def func_2(x, y): a = 2 * x @@ -106,18 +109,36 @@ def func_2(x, y): cell_8_source = remove_imports_defs_and_globals(inspect.getsource(cell_8)) error_cell_source = remove_imports_defs_and_globals(inspect.getsource(error_cell)) -cell_1_renderer = handcalcs.handcalcs.LatexRenderer(cell_1_source, cell_1.calc_results, line_args) -cell_2_renderer = handcalcs.handcalcs.LatexRenderer(cell_2_source, cell_2.calc_results, line_args) +cell_1_renderer = handcalcs.handcalcs.LatexRenderer( + cell_1_source, cell_1.calc_results, line_args +) +cell_2_renderer = handcalcs.handcalcs.LatexRenderer( + cell_2_source, cell_2.calc_results, line_args +) cell_2b_renderer = handcalcs.handcalcs.LatexRenderer( cell_2b_source, cell_2b.calc_results, line_args ) -cell_3_renderer = handcalcs.handcalcs.LatexRenderer(cell_3_source, cell_3.calc_results, line_args) -cell_4_renderer = handcalcs.handcalcs.LatexRenderer(cell_4_source, cell_4.calc_results, line_args_params) -cell_5_renderer = handcalcs.handcalcs.LatexRenderer(cell_5_source, cell_5.calc_results, line_args) -cell_6_renderer = handcalcs.handcalcs.LatexRenderer(cell_6_source, cell_6.calc_results, line_args) -cell_7_renderer = handcalcs.handcalcs.LatexRenderer(cell_7_source, cell_7.calc_results, line_args_long) -cell_7b_renderer = handcalcs.handcalcs.LatexRenderer(cell_7b_source, cell_7b.calc_results, line_args_short) -cell_8_renderer = handcalcs.handcalcs.LatexRenderer(cell_8_source, cell_8.calc_results, line_args_symbolic) +cell_3_renderer = handcalcs.handcalcs.LatexRenderer( + cell_3_source, cell_3.calc_results, line_args +) +cell_4_renderer = handcalcs.handcalcs.LatexRenderer( + cell_4_source, cell_4.calc_results, line_args_params +) +cell_5_renderer = handcalcs.handcalcs.LatexRenderer( + cell_5_source, cell_5.calc_results, line_args +) +cell_6_renderer = handcalcs.handcalcs.LatexRenderer( + cell_6_source, cell_6.calc_results, line_args +) +cell_7_renderer = handcalcs.handcalcs.LatexRenderer( + cell_7_source, cell_7.calc_results, line_args_long +) +cell_7b_renderer = handcalcs.handcalcs.LatexRenderer( + cell_7b_source, cell_7b.calc_results, line_args_short +) +cell_8_renderer = handcalcs.handcalcs.LatexRenderer( + cell_8_source, cell_8.calc_results, line_args_symbolic +) error_cell_renderer = handcalcs.handcalcs.LatexRenderer( error_cell_source, error_cell.calc_results, line_args ) @@ -129,35 +150,35 @@ def func_2(x, y): def test_integration(): assert ( cell_1_renderer.render() - == '\\[\n\\begin{aligned}\na &= 2\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\ny &= 6\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\n\\alpha_{\\eta_{\\psi}} &= \\frac{ 4 }{ \\left( y \\right) ^{ \\left( a + 1 \\right) } } = \\frac{ 4 }{ \\left( 6 \\right) ^{ \\left( 2 + 1 \\right) } } &= 0.019\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\n\\alpha_{\\eta_{\\psi}} &= 0.019\\;\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\na &= 2\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\ny &= 6\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\n\\alpha_{\\eta_{\\psi}} &= \\frac{ 4 }{ \\left( y \\right) ^{ \\left( a + 1 \\right) } } = \\frac{ 4 }{ \\left( 6 \\right) ^{ \\left( 2 + 1 \\right) } } &= 0.019\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\n\\alpha_{\\eta_{\\psi}} &= 0.019\\;\n\\end{aligned}\n\\]" ) assert ( cell_2_renderer.render() - == '\\[\n\\begin{aligned}\nx &= 2\\;\n\\\\[10pt]\n&\\text{Since, }x \\geq 1 \\rightarrow \\left( 2 \\geq 1 \\right):\\;\\;\\textrm{(Comment)}\\end{aligned}\n\\]\n\\[\n\\begin{aligned}\nb &= x \\cdot 1 = 2 \\cdot 1 &= 2\n\\\\\nc &= 2\\;\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\nx &= 2\\;\n\\\\[10pt]\n&\\text{Since, }x \\geq 1 \\rightarrow \\left( 2 \\geq 1 \\right):\\;\\;\\textrm{(Comment)}\\end{aligned}\n\\]\n\\[\n\\begin{aligned}\nb &= x \\cdot 1 = 2 \\cdot 1 &= 2\n\\\\\nc &= 2\\;\n\\end{aligned}\n\\]" ) assert ( cell_2b_renderer.render() - == '\\[\n\\begin{aligned}\nx &= 10\\;\n\\\\[10pt]\nb &= x \\cdot 1 = 10 \\cdot 1 &= 10\n\\\\\nc &= 10\\;\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\nx &= 10\\;\n\\\\[10pt]\nb &= x \\cdot 1 = 10 \\cdot 1 &= 10\n\\\\\nc &= 10\\;\n\\end{aligned}\n\\]" ) assert ( cell_3_renderer.render() - == '\\[\n\\begin{aligned}\ny &= -2\\;\n\\\\[10pt]\nb &= 3\\;\n\\\\[10pt]\nc &= 4\\;\n\\\\[10pt]\n\\alpha_{\\eta_{\\psi}} &= 23\\;\n\\\\[10pt]\nd &= \\sqrt { \\frac{ 1 }{ \\frac{ b }{ c }} } = \\sqrt { \\frac{ 1 }{ \\frac{ 3 }{ 4 }} } &= 0.289\n\\\\[10pt]\nf &= \\operatorname{ceil} { \\left( \\alpha_{\\eta_{\\psi}} + 1 \\right) \\bmod 2 } = \\operatorname{ceil} { \\left( 23 + 1 \\right) \\bmod 2 } &= 0\n\\\\[10pt]\ng &= \\int_{ y } ^ { b } \\left( x \\right) ^{ 2 } + 3 \\cdot x \\; dx = \\int_{ -2 } ^ { 3 } \\left( x \\right) ^{ 2 } + 3 \\cdot x \\; dx &= [42,\\ 0.001]\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\ny &= -2\\;\n\\\\[10pt]\nb &= 3\\;\n\\\\[10pt]\nc &= 4\\;\n\\\\[10pt]\n\\alpha_{\\eta_{\\psi}} &= 23\\;\n\\\\[10pt]\nd &= \\sqrt { \\frac{ 1 }{ \\frac{ b }{ c }} } = \\sqrt { \\frac{ 1 }{ \\frac{ 3 }{ 4 }} } &= 0.289\n\\\\[10pt]\nf &= \\operatorname{ceil} { \\left( \\alpha_{\\eta_{\\psi}} + 1 \\right) \\bmod 2 } = \\operatorname{ceil} { \\left( 23 + 1 \\right) \\bmod 2 } &= 0\n\\\\[10pt]\ng &= \\int_{ y } ^ { b } \\left( x \\right) ^{ 2 } + 3 \\cdot x \\; dx = \\int_{ -2 } ^ { 3 } \\left( x \\right) ^{ 2 } + 3 \\cdot x \\; dx &= [42,\\ 0.001]\n\\end{aligned}\n\\]" ) assert ( cell_4_renderer.render() - == '\\[\n\\begin{aligned}\na &= 2 &b &= 3 &c &= 5\\\\\n y &= 6\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\na &= 2 &b &= 3 &c &= 5\\\\\n y &= 6\n\\end{aligned}\n\\]" ) assert ( cell_5_renderer.render() - == '\\[\n\\begin{aligned}\na &= 10000001\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\nb &= 20000002\\;\n\\\\[10pt]\nc &= 30000003\\;\n\\\\[10pt]\nx &= 5\\;\n\\\\[10pt]\ny &= \\sqrt { \\frac{ a }{ b } } + \\arcsin { \\sin { \\left( \\frac{ b }{ c } \\right) } } + \\left( \\frac{ a }{ b } \\right) ^{ \\mathrm{0.5} } + \\sqrt { \\frac{ a \\cdot b + b \\cdot c }{ \\left( b \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ a }{ b } \\right) } \\\\&= \\sqrt { \\frac{ 10000001 }{ 20000002 } } + \\arcsin { \\sin { \\left( \\frac{ 20000002 }{ 30000003 } \\right) } } + \\left( \\frac{ 10000001 }{ 20000002 } \\right) ^{ 0.5 } + \\sqrt { \\frac{ 10000001 \\cdot 20000002 + 20000002 \\cdot 30000003 }{ \\left( 20000002 \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ 10000001 }{ 20000002 } \\right) } \\\\&= 3.975\\;\\;\\textrm{(Comment)}\\\\\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\na &= 10000001\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\nb &= 20000002\\;\n\\\\[10pt]\nc &= 30000003\\;\n\\\\[10pt]\nx &= 5\\;\n\\\\[10pt]\ny &= \\sqrt { \\frac{ a }{ b } } + \\arcsin { \\sin { \\left( \\frac{ b }{ c } \\right) } } + \\left( \\frac{ a }{ b } \\right) ^{ \\mathrm{0.5} } + \\sqrt { \\frac{ a \\cdot b + b \\cdot c }{ \\left( b \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ a }{ b } \\right) } \\\\&= \\sqrt { \\frac{ 10000001 }{ 20000002 } } + \\arcsin { \\sin { \\left( \\frac{ 20000002 }{ 30000003 } \\right) } } + \\left( \\frac{ 10000001 }{ 20000002 } \\right) ^{ 0.5 } + \\sqrt { \\frac{ 10000001 \\cdot 20000002 + 20000002 \\cdot 30000003 }{ \\left( 20000002 \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ 10000001 }{ 20000002 } \\right) } \\\\&= 3.975\\;\\;\\textrm{(Comment)}\\\\\n\\end{aligned}\n\\]" ) assert ( cell_6_renderer.render() - == '\\[\n\\begin{aligned}\na &= 2\\;\n\\\\[10pt]\nb &= 3 \\cdot a \\\\&= 3 \\cdot 2 \\\\&= 6\\\\\n\\\\[10pt]\ny &= 2 \\cdot a + 4 + 3 \\\\&= 2 \\cdot 2 + 4 + 3 \\\\&= 11\\\\\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\na &= 2\\;\n\\\\[10pt]\nb &= 3 \\cdot a \\\\&= 3 \\cdot 2 \\\\&= 6\\\\\n\\\\[10pt]\ny &= 2 \\cdot a + 4 + 3 \\\\&= 2 \\cdot 2 + 4 + 3 \\\\&= 11\\\\\n\\end{aligned}\n\\]" ) assert ( cell_7_renderer.render() - == '\\[\n\\begin{aligned}\na &= 23\\;\n\\\\[10pt]\nb &= 43\\;\n\\\\[10pt]\nc &= 52\\;\n\\\\[10pt]\nf &= \\frac{ c }{ a } + b \\\\&= \\frac{ 52 }{ 23 } + 43 \\\\&= 45.26\\;\\;\\textrm{(Comment)}\\\\\n\\\\[10pt]\ng &= c \\cdot \\frac{ f }{ a } \\\\&= 52 \\cdot \\frac{ 45.26 }{ 23 } \\\\&= 102.33\\;\\;\\textrm{(Comment)}\\\\\n\\\\[10pt]\nd &= \\sqrt { \\frac{ a }{ b } } + \\arcsin { \\sin { \\left( \\frac{ b }{ c } \\right) } } + \\left( \\frac{ a }{ b } \\right) ^{ \\mathrm{0.5} } + \\sqrt { \\frac{ a \\cdot b + b \\cdot c }{ \\left( b \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ a }{ b } \\right) } \\\\&= \\sqrt { \\frac{ 23 }{ 43 } } + \\arcsin { \\sin { \\left( \\frac{ 43 }{ 52 } \\right) } } + \\left( \\frac{ 23 }{ 43 } \\right) ^{ 0.5 } + \\sqrt { \\frac{ 23 \\cdot 43 + 43 \\cdot 52 }{ \\left( 43 \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ 23 }{ 43 } \\right) } \\\\&= 4.12\\;\\;\\textrm{(Comment)}\\\\\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\na &= 23\\;\n\\\\[10pt]\nb &= 43\\;\n\\\\[10pt]\nc &= 52\\;\n\\\\[10pt]\nf &= \\frac{ c }{ a } + b \\\\&= \\frac{ 52 }{ 23 } + 43 \\\\&= 45.26\\;\\;\\textrm{(Comment)}\\\\\n\\\\[10pt]\ng &= c \\cdot \\frac{ f }{ a } \\\\&= 52 \\cdot \\frac{ 45.26 }{ 23 } \\\\&= 102.33\\;\\;\\textrm{(Comment)}\\\\\n\\\\[10pt]\nd &= \\sqrt { \\frac{ a }{ b } } + \\arcsin { \\sin { \\left( \\frac{ b }{ c } \\right) } } + \\left( \\frac{ a }{ b } \\right) ^{ \\mathrm{0.5} } + \\sqrt { \\frac{ a \\cdot b + b \\cdot c }{ \\left( b \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ a }{ b } \\right) } \\\\&= \\sqrt { \\frac{ 23 }{ 43 } } + \\arcsin { \\sin { \\left( \\frac{ 43 }{ 52 } \\right) } } + \\left( \\frac{ 23 }{ 43 } \\right) ^{ 0.5 } + \\sqrt { \\frac{ 23 \\cdot 43 + 43 \\cdot 52 }{ \\left( 43 \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ 23 }{ 43 } \\right) } \\\\&= 4.12\\;\\;\\textrm{(Comment)}\\\\\n\\end{aligned}\n\\]" ) assert ( cell_7b_renderer.render() @@ -165,14 +186,19 @@ def test_integration(): ) assert ( cell_8_renderer.render() - == '\\[\n\\begin{aligned}\na &= \\mathrm{23}\\;\n\\\\[10pt]\nb &= \\mathrm{43}\\;\n\\\\[10pt]\nc &= \\mathrm{52}\\;\n\\\\[10pt]\nf &= \\frac{ c }{ a } + b\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\ng &= c \\cdot \\frac{ f }{ a }\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\nd &= \\sqrt { \\frac{ a }{ b } } + \\arcsin { \\sin { \\left( \\frac{ b }{ c } \\right) } } + \\left( \\frac{ a }{ b } \\right) ^{ \\mathrm{0.5} } + \\sqrt { \\frac{ a \\cdot b + b \\cdot c }{ \\left( b \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ a }{ b } \\right) }\\;\\;\\textrm{(Comment)}\n\\end{aligned}\n\\]' + == "\\[\n\\begin{aligned}\na &= \\mathrm{23}\\;\n\\\\[10pt]\nb &= \\mathrm{43}\\;\n\\\\[10pt]\nc &= \\mathrm{52}\\;\n\\\\[10pt]\nf &= \\frac{ c }{ a } + b\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\ng &= c \\cdot \\frac{ f }{ a }\\;\\;\\textrm{(Comment)}\n\\\\[10pt]\nd &= \\sqrt { \\frac{ a }{ b } } + \\arcsin { \\sin { \\left( \\frac{ b }{ c } \\right) } } + \\left( \\frac{ a }{ b } \\right) ^{ \\mathrm{0.5} } + \\sqrt { \\frac{ a \\cdot b + b \\cdot c }{ \\left( b \\right) ^{ 2 } } } + \\sin { \\left( \\frac{ a }{ b } \\right) }\\;\\;\\textrm{(Comment)}\n\\end{aligned}\n\\]" ) + # Test decorator.py + def test_handcalc(): - assert func_1(4, 5) == ('\n\\begin{aligned}\na &= 2 \\cdot x = 2 \\cdot 4 &= 8\n\\10pt]\nb &= 3 \\cdot a + y = 3 \\cdot 8 + 5 &= 29\n\\end{aligned}\n', - {'x': 4, 'y': 5, 'a': 8, 'b': 29}) + assert func_1(4, 5) == ( + "\n\\begin{aligned}\na &= 2 \\cdot x = 2 \\cdot 4 &= 8\n\\10pt]\nb &= 3 \\cdot a + y = 3 \\cdot 8 + 5 &= 29\n\\end{aligned}\n", + {"x": 4, "y": 5, "a": 8, "b": 29}, + ) + def test_handcalc2(): assert func_2(4, 5) == {"x": 4, "y": 5, "a": 8, "b": 29} @@ -180,49 +206,63 @@ def test_handcalc2(): # Test template.py + def test_install_latex(capsys): HERE = pathlib.Path(__file__).resolve().parent - TEMPLATES = HERE.parent/ "handcalcs" / "templates" - MAIN_TEMPLATE = TEMPLATES / "latex" / 't-makaro_classic_romanoutput_noinput.tplx' - NBCONVERT_TEMPLATES_DIR = pathlib.Path(nbconvert.__file__).resolve().parent / "templates" / "latex" + TEMPLATES = HERE.parent / "handcalcs" / "templates" + MAIN_TEMPLATE = TEMPLATES / "latex" / "t-makaro_classic_romanoutput_noinput.tplx" + NBCONVERT_TEMPLATES_DIR = ( + pathlib.Path(nbconvert.__file__).resolve().parent / "templates" / "latex" + ) install_latex() captured = capsys.readouterr() - assert captured.out == "Available templates: \n ['t-makaro_classic_romanoutput_noinput.tplx']\n" - install_latex(swap_in ='t-makaro_classic_romanoutput_noinput.tplx') + assert ( + captured.out + == "Available templates: \n ['t-makaro_classic_romanoutput_noinput.tplx']\n" + ) + install_latex(swap_in="t-makaro_classic_romanoutput_noinput.tplx") assert filecmp.cmp(MAIN_TEMPLATE, NBCONVERT_TEMPLATES_DIR / "article.tplx") + def test_install_latex_restore(): HERE = pathlib.Path(__file__).resolve().parent - TEMPLATES = HERE.parent/ "handcalcs" / "templates" - MAIN_TEMPLATE = TEMPLATES / "latex" / 't-makaro_classic_romanoutput_noinput.tplx' - NBCONVERT_TEMPLATES_DIR = pathlib.Path(nbconvert.__file__).resolve().parent / "templates" / "latex" + TEMPLATES = HERE.parent / "handcalcs" / "templates" + MAIN_TEMPLATE = TEMPLATES / "latex" / "t-makaro_classic_romanoutput_noinput.tplx" + NBCONVERT_TEMPLATES_DIR = ( + pathlib.Path(nbconvert.__file__).resolve().parent / "templates" / "latex" + ) install_latex(restore=True) assert not filecmp.cmp(MAIN_TEMPLATE, NBCONVERT_TEMPLATES_DIR / "article.tplx") + def test_install_html(capsys): HERE = pathlib.Path(__file__).resolve().parent - TEMPLATES = HERE.parent/ "handcalcs" / "templates" - MAIN_TEMPLATE = TEMPLATES / "html" / 'full_html_noinputs.tpl' - NBCONVERT_TEMPLATES_DIR = pathlib.Path(nbconvert.__file__).resolve().parent / "templates" / "html" + TEMPLATES = HERE.parent / "handcalcs" / "templates" + MAIN_TEMPLATE = TEMPLATES / "html" / "full_html_noinputs.tpl" + NBCONVERT_TEMPLATES_DIR = ( + pathlib.Path(nbconvert.__file__).resolve().parent / "templates" / "html" + ) install_html() captured = capsys.readouterr() assert captured.out == "Available templates: \n ['full_html_noinputs.tpl']\n" - install_html(swap_in ='full_html_noinputs.tpl') + install_html(swap_in="full_html_noinputs.tpl") assert filecmp.cmp(MAIN_TEMPLATE, NBCONVERT_TEMPLATES_DIR / "full.tpl") + def test_install_html_restore(): HERE = pathlib.Path(__file__).resolve().parent - TEMPLATES = HERE.parent/ "handcalcs" / "templates" - MAIN_TEMPLATE = TEMPLATES / "html" / 'full_html_noinputs.tpl' - NBCONVERT_TEMPLATES_DIR = pathlib.Path(nbconvert.__file__).resolve().parent / "templates" / "html" + TEMPLATES = HERE.parent / "handcalcs" / "templates" + MAIN_TEMPLATE = TEMPLATES / "html" / "full_html_noinputs.tpl" + NBCONVERT_TEMPLATES_DIR = ( + pathlib.Path(nbconvert.__file__).resolve().parent / "templates" / "html" + ) install_html(restore=True) assert not filecmp.cmp(MAIN_TEMPLATE, NBCONVERT_TEMPLATES_DIR / "full.tpl") - # Test expected exceptions @@ -377,7 +417,6 @@ def test_add_result_values_to_lines(): ) - def test_round_and_render_line_objects_to_latex(): assert handcalcs.handcalcs.round_and_render_line_objects_to_latex( CalcLine( @@ -1160,18 +1199,39 @@ def test_test_for_scientific_notation_str(): assert handcalcs.handcalcs.test_for_scientific_notation_str("e10") == False assert handcalcs.handcalcs.test_for_scientific_notation_str("-1.23e4") == True + def test_swap_scientific_notation_float(): - assert handcalcs.handcalcs.swap_scientific_notation_float(deque([0.0000001, + 0.132]), 3) == deque(["1.000e-07", 0.132]) - assert handcalcs.handcalcs.swap_scientific_notation_float(deque([0.000000135, + 2.30]), 3) == deque(["1.350e-07", 2.30]) - assert handcalcs.handcalcs.swap_scientific_notation_float(deque([0.013546, + 0.132]),5) == deque(["1.35460e-02", '1.32000e-01']) + assert handcalcs.handcalcs.swap_scientific_notation_float( + deque([0.0000001, +0.132]), 3 + ) == deque(["1.000e-07", 0.132]) + assert handcalcs.handcalcs.swap_scientific_notation_float( + deque([0.000000135, +2.30]), 3 + ) == deque(["1.350e-07", 2.30]) + assert handcalcs.handcalcs.swap_scientific_notation_float( + deque([0.013546, +0.132]), 5 + ) == deque(["1.35460e-02", "1.32000e-01"]) + def test_swap_comparison_ops(): - assert handcalcs.handcalcs.swap_comparison_ops(deque(["3", ">", "1"])) == deque(["3", "\\gt", "1"]) - assert handcalcs.handcalcs.swap_comparison_ops(deque(["3", ">=", "1"])) == deque(["3", "\\geq", "1"]) - assert handcalcs.handcalcs.swap_comparison_ops(deque(["3", "==", "1"])) == deque(["3", "=", "1"]) - assert handcalcs.handcalcs.swap_comparison_ops(deque(["3", "!=", "1"])) == deque(["3", "\\neq", "1"]) - assert handcalcs.handcalcs.swap_comparison_ops(deque(["a", "=", deque(["1", "<", "5"])])) == deque(["a", "=", deque(["1", "\\lt", "5"])]) - assert handcalcs.handcalcs.swap_comparison_ops(deque(["a", "=", deque(["1", "<=", "5"])])) == deque(["a", "=", deque(["1", "\\leq", "5"])]) + assert handcalcs.handcalcs.swap_comparison_ops(deque(["3", ">", "1"])) == deque( + ["3", "\\gt", "1"] + ) + assert handcalcs.handcalcs.swap_comparison_ops(deque(["3", ">=", "1"])) == deque( + ["3", "\\geq", "1"] + ) + assert handcalcs.handcalcs.swap_comparison_ops(deque(["3", "==", "1"])) == deque( + ["3", "=", "1"] + ) + assert handcalcs.handcalcs.swap_comparison_ops(deque(["3", "!=", "1"])) == deque( + ["3", "\\neq", "1"] + ) + assert handcalcs.handcalcs.swap_comparison_ops( + deque(["a", "=", deque(["1", "<", "5"])]) + ) == deque(["a", "=", deque(["1", "\\lt", "5"])]) + assert handcalcs.handcalcs.swap_comparison_ops( + deque(["a", "=", deque(["1", "<=", "5"])]) + ) == deque(["a", "=", deque(["1", "\\leq", "5"])]) + def test_test_for_long_var_strs(): assert handcalcs.handcalcs.test_for_long_var_strs("x_y_a") == False @@ -1185,97 +1245,274 @@ def test_test_for_long_var_strs(): assert handcalcs.handcalcs.test_for_long_var_strs("\\frac{") == False assert handcalcs.handcalcs.test_for_long_var_strs("\\sin") == False + def test_swap_long_var_strs(): - assert handcalcs.handcalcs.swap_long_var_strs(deque(["cat_xy_u", "+", 4])) == deque(['\\mathrm{cat}_xy_u', '+', 4]) - assert handcalcs.handcalcs.swap_long_var_strs(deque(["RATE", "*", "4"])) == deque(['\\mathrm{RATE}', '*', '4']) - assert handcalcs.handcalcs.swap_long_var_strs(deque(["\\sin", "\\left(", "apple_cart", "\\right)"])) == deque(['\\sin', '\\left(', '\\mathrm{apple}_cart', '\\right)']) - assert handcalcs.handcalcs.swap_long_var_strs(deque(["x" , "=", "a", "*", deque(["b", "+", "annual_x"])])) == deque(['x', '=', 'a', '*', deque(['b', '+', '\\mathrm{annual}_x'])]) + assert handcalcs.handcalcs.swap_long_var_strs(deque(["cat_xy_u", "+", 4])) == deque( + ["\\mathrm{cat}_xy_u", "+", 4] + ) + assert handcalcs.handcalcs.swap_long_var_strs(deque(["RATE", "*", "4"])) == deque( + ["\\mathrm{RATE}", "*", "4"] + ) + assert handcalcs.handcalcs.swap_long_var_strs( + deque(["\\sin", "\\left(", "apple_cart", "\\right)"]) + ) == deque(["\\sin", "\\left(", "\\mathrm{apple}_cart", "\\right)"]) + assert handcalcs.handcalcs.swap_long_var_strs( + deque(["x", "=", "a", "*", deque(["b", "+", "annual_x"])]) + ) == deque(["x", "=", "a", "*", deque(["b", "+", "\\mathrm{annual}_x"])]) def test_test_for_function_special_case(): - assert handcalcs.handcalcs.test_for_function_special_case(deque(["sin", "45"])) == True - assert handcalcs.handcalcs.test_for_function_special_case(deque(["sin", deque(["a", "/", "b"])])) == True - assert handcalcs.handcalcs.test_for_function_special_case(deque(["1", "+", "b"])) == False - assert handcalcs.handcalcs.test_for_function_special_case(deque(["-", "a"])) == False - assert handcalcs.handcalcs.test_for_function_special_case(deque(["sin", deque(["tan", deque(["a", "/", "b"])])])) == True + assert ( + handcalcs.handcalcs.test_for_function_special_case(deque(["sin", "45"])) == True + ) + assert ( + handcalcs.handcalcs.test_for_function_special_case( + deque(["sin", deque(["a", "/", "b"])]) + ) + == True + ) + assert ( + handcalcs.handcalcs.test_for_function_special_case(deque(["1", "+", "b"])) + == False + ) + assert ( + handcalcs.handcalcs.test_for_function_special_case(deque(["-", "a"])) == False + ) + assert ( + handcalcs.handcalcs.test_for_function_special_case( + deque(["sin", deque(["tan", deque(["a", "/", "b"])])]) + ) + == True + ) def test_get_function_name(): assert handcalcs.handcalcs.get_function_name(deque(["sin", "45"])) == "sin" - assert handcalcs.handcalcs.get_function_name(deque(["sin", deque(["a", "/", "b"])])) == "sin" - assert handcalcs.handcalcs.get_function_name(deque(["1", "+", "b"])) =="" + assert ( + handcalcs.handcalcs.get_function_name(deque(["sin", deque(["a", "/", "b"])])) + == "sin" + ) + assert handcalcs.handcalcs.get_function_name(deque(["1", "+", "b"])) == "" assert handcalcs.handcalcs.get_function_name(deque(["-", "a"])) == "" - assert handcalcs.handcalcs.get_function_name(deque(["double", deque(["tan", deque(["a", "/", "b"])])])) == "double" + assert ( + handcalcs.handcalcs.get_function_name( + deque(["double", deque(["tan", deque(["a", "/", "b"])])]) + ) + == "double" + ) assert handcalcs.handcalcs.get_function_name(deque(["1", "+", "b", "*", "4"])) == "" def test_test_for_fraction_exception(): - assert handcalcs.handcalcs.test_for_fraction_exception(deque(['sin', '45']), '/') == True - assert handcalcs.handcalcs.test_for_fraction_exception('/', deque(['a', '+', 'b'])) == True - assert handcalcs.handcalcs.test_for_fraction_exception(deque(['a', '+', 'b']), '*') == False - assert handcalcs.handcalcs.test_for_fraction_exception(deque(['-', '1']), '/') == True + assert ( + handcalcs.handcalcs.test_for_fraction_exception(deque(["sin", "45"]), "/") + == True + ) + assert ( + handcalcs.handcalcs.test_for_fraction_exception("/", deque(["a", "+", "b"])) + == True + ) + assert ( + handcalcs.handcalcs.test_for_fraction_exception(deque(["a", "+", "b"]), "*") + == False + ) + assert ( + handcalcs.handcalcs.test_for_fraction_exception(deque(["-", "1"]), "/") == True + ) def test_insert_function_parentheses(): - assert handcalcs.handcalcs.insert_function_parentheses(deque(["sin", "45"])) == deque(["sin", "\\left(", "45", "\\right)"]) - assert handcalcs.handcalcs.insert_function_parentheses(deque(["sin", deque(["a", "/", "b"])])) == deque(["sin", "\\left(", deque(["a", "/", "b"]), "\\right)"]) - assert handcalcs.handcalcs.insert_function_parentheses(deque(["double", deque(["tan", deque(["a", "/", "b"])])])) == deque(["double", "\\left(", deque(["tan", deque(["a", "/", "b"])]), "\\right)"]) + assert handcalcs.handcalcs.insert_function_parentheses( + deque(["sin", "45"]) + ) == deque(["sin", "\\left(", "45", "\\right)"]) + assert handcalcs.handcalcs.insert_function_parentheses( + deque(["sin", deque(["a", "/", "b"])]) + ) == deque(["sin", "\\left(", deque(["a", "/", "b"]), "\\right)"]) + assert handcalcs.handcalcs.insert_function_parentheses( + deque(["double", deque(["tan", deque(["a", "/", "b"])])]) + ) == deque( + ["double", "\\left(", deque(["tan", deque(["a", "/", "b"])]), "\\right)"] + ) def test_test_for_unary(): assert handcalcs.handcalcs.test_for_unary(deque(["-", "1"])) == True assert handcalcs.handcalcs.test_for_unary(deque(["+", "5"])) == True assert handcalcs.handcalcs.test_for_unary(deque(["1", "+", "5"])) == False - assert handcalcs.handcalcs.test_for_unary(deque(["-", deque(["sin", "45"])])) == True + assert ( + handcalcs.handcalcs.test_for_unary(deque(["-", deque(["sin", "45"])])) == True + ) def test_insert_unary_parentheses(): - assert handcalcs.handcalcs.insert_unary_parentheses(deque(["-", "1"])) == deque(["\\left(", "-", "1", "\\right)"]) - assert handcalcs.handcalcs.insert_unary_parentheses(deque(["+", "1"])) == deque(["\\left(", "+", "1", "\\right)"]) - assert handcalcs.handcalcs.insert_unary_parentheses(deque(["1", "+", "sin", "45"])) == deque(["\\left(", "1", "+", "sin", "45", "\\right)"]) + assert handcalcs.handcalcs.insert_unary_parentheses(deque(["-", "1"])) == deque( + ["\\left(", "-", "1", "\\right)"] + ) + assert handcalcs.handcalcs.insert_unary_parentheses(deque(["+", "1"])) == deque( + ["\\left(", "+", "1", "\\right)"] + ) + assert handcalcs.handcalcs.insert_unary_parentheses( + deque(["1", "+", "sin", "45"]) + ) == deque(["\\left(", "1", "+", "sin", "45", "\\right)"]) def test_insert_func_braces(): - assert handcalcs.handcalcs.insert_func_braces(deque(["sin", "\\left(", "45", "\\right)"])) == deque(["sin", "{", "\\left(", "45", "\\right)", "}"]) - assert handcalcs.handcalcs.insert_func_braces(deque(["sin", "\\left(", deque(["a", "/", "b"]), "\\right)"])) == deque(["sin","{", "\\left(", deque(["a", "/", "b"]), "\\right)", "}"]) - assert handcalcs.handcalcs.insert_func_braces(deque(['sqrt', deque(['b', '+', 'b'])])) == deque(['sqrt', '{', deque(['b', '+', 'b']), '}']) + assert handcalcs.handcalcs.insert_func_braces( + deque(["sin", "\\left(", "45", "\\right)"]) + ) == deque(["sin", "{", "\\left(", "45", "\\right)", "}"]) + assert handcalcs.handcalcs.insert_func_braces( + deque(["sin", "\\left(", deque(["a", "/", "b"]), "\\right)"]) + ) == deque(["sin", "{", "\\left(", deque(["a", "/", "b"]), "\\right)", "}"]) + assert handcalcs.handcalcs.insert_func_braces( + deque(["sqrt", deque(["b", "+", "b"])]) + ) == deque(["sqrt", "{", deque(["b", "+", "b"]), "}"]) + def test_get_func_latex(): assert handcalcs.handcalcs.get_func_latex("sin") == "\\sin" assert handcalcs.handcalcs.get_func_latex("sum") == "\\Sigma" assert handcalcs.handcalcs.get_func_latex("double2") == "double2" + def test_func_name(): - assert handcalcs.handcalcs.swap_func_name(deque(["sin", deque(["a", "/", "b"])]), "sin") == deque(["\\sin", deque(["a", "/", "b"])]) - assert handcalcs.handcalcs.swap_func_name(deque(["tan", "45"]), "tan") == deque(["\\tan", "45"]) - assert handcalcs.handcalcs.swap_func_name(deque(["sum", deque(["a", ",", "b", "," , "c", ",", "d"])]), "sum") == deque(["\\Sigma", deque(["a", ",", "b", "," , "c", ",", "d"])]) + assert handcalcs.handcalcs.swap_func_name( + deque(["sin", deque(["a", "/", "b"])]), "sin" + ) == deque(["\\sin", deque(["a", "/", "b"])]) + assert handcalcs.handcalcs.swap_func_name(deque(["tan", "45"]), "tan") == deque( + ["\\tan", "45"] + ) + assert handcalcs.handcalcs.swap_func_name( + deque(["sum", deque(["a", ",", "b", ",", "c", ",", "d"])]), "sum" + ) == deque(["\\Sigma", deque(["a", ",", "b", ",", "c", ",", "d"])]) + def test_swap_math_funcs(): - assert handcalcs.handcalcs.swap_math_funcs(deque(["z" , "=", deque(["double", "\\left(", deque(["a", "/", "b"]), "\\right)"])]), dict()) == deque(['z','=',deque(['\\operatorname{double}','{','\\left(',deque(['a', '/', 'b']),'\\right)','}'])]) - assert handcalcs.handcalcs.swap_math_funcs(deque(["rate", "=", deque(["sin", "\\left(", "a", "\\right)"])]), dict()) == deque(['rate', '=', deque(['\\sin', '{', '\\left(', 'a', '\\right)', '}'])]) - assert handcalcs.handcalcs.swap_math_funcs(deque(["test", "=", deque(["sqrt", deque(["b", "+", "b"])])]), dict()) == deque(["test", "=", deque(["\\sqrt", "{", deque(["b", "+", "b"]), "}"])]) + assert handcalcs.handcalcs.swap_math_funcs( + deque( + ["z", "=", deque(["double", "\\left(", deque(["a", "/", "b"]), "\\right)"])] + ), + dict(), + ) == deque( + [ + "z", + "=", + deque( + [ + "\\operatorname{double}", + "{", + "\\left(", + deque(["a", "/", "b"]), + "\\right)", + "}", + ] + ), + ] + ) + assert handcalcs.handcalcs.swap_math_funcs( + deque(["rate", "=", deque(["sin", "\\left(", "a", "\\right)"])]), dict() + ) == deque(["rate", "=", deque(["\\sin", "{", "\\left(", "a", "\\right)", "}"])]) + assert handcalcs.handcalcs.swap_math_funcs( + deque(["test", "=", deque(["sqrt", deque(["b", "+", "b"])])]), dict() + ) == deque(["test", "=", deque(["\\sqrt", "{", deque(["b", "+", "b"]), "}"])]) + def test_test_for_typ_arithmetic(): assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(["sin", "45"])) == False - assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(["sin", deque(["a", "/", "b"])])) == False + assert ( + handcalcs.handcalcs.test_for_typ_arithmetic( + deque(["sin", deque(["a", "/", "b"])]) + ) + == False + ) assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(["1", "+", "b"])) == True assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(["-", "a"])) == False - assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(["sin", deque(["tan", deque(["a", "/", "b"])])])) == False - assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(["1", "+", "b", "*", "4"])) == True - assert handcalcs.handcalcs.test_for_typ_arithmetic(deque([deque(["-", "a"]), "+", "b"])) == True - assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(['\\left(', 'able', '+', 'b', '\\right)'])) == True - assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(["double", "\\left(", deque(["a", "/", "b"]), "\\right)"])) == False - assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(['c', '**', '2'])) == True + assert ( + handcalcs.handcalcs.test_for_typ_arithmetic( + deque(["sin", deque(["tan", deque(["a", "/", "b"])])]) + ) + == False + ) + assert ( + handcalcs.handcalcs.test_for_typ_arithmetic(deque(["1", "+", "b", "*", "4"])) + == True + ) + assert ( + handcalcs.handcalcs.test_for_typ_arithmetic( + deque([deque(["-", "a"]), "+", "b"]) + ) + == True + ) + assert ( + handcalcs.handcalcs.test_for_typ_arithmetic( + deque(["\\left(", "able", "+", "b", "\\right)"]) + ) + == True + ) + assert ( + handcalcs.handcalcs.test_for_typ_arithmetic( + deque(["double", "\\left(", deque(["a", "/", "b"]), "\\right)"]) + ) + == False + ) + assert handcalcs.handcalcs.test_for_typ_arithmetic(deque(["c", "**", "2"])) == True + def test_expr_parser(): - assert handcalcs.handcalcs.expr_parser("z = sqrt(5)") == deque(['z', '=', deque(['sqrt', '5'])]) - assert handcalcs.handcalcs.expr_parser("z = (sqrt(5))") == deque(['z', '=', deque(['sqrt', '5'])]) - assert handcalcs.handcalcs.expr_parser("z = x**2/sqrt(2)") == deque(['z', '=', deque(['x', '**', '2']), '/', deque(['sqrt', '2'])]) - assert handcalcs.handcalcs.expr_parser("e1_nu = 1.25e-5 +-1 **2/sum(3,4,5)") == deque(['e1_nu', '=', '1.25e-5', '+', deque(['-', deque(['1', '**', '2'])]), '/', deque(['sum', deque(['3', ',', '4', ',', '5'])])]) - assert handcalcs.handcalcs.expr_parser("e1_nu = -1.25e5 +- 1") == deque(['e1_nu', '=', deque(['-', '1.25e5']), '+', deque(['-', '1'])]) - assert handcalcs.handcalcs.expr_parser("e1_nu = kN.to('ksf')") == deque(['e1_nu', '=', 'kN']) - assert handcalcs.handcalcs.expr_parser("e1_nu = 1.25e5+1.25e-5j **(a/b**2)/sum(3,4,5)") == deque(['e1_nu', '=', deque(['1.25e5+1.25e-5j', '**', deque(['a', '/', deque(['b', '**', '2'])])]), '/', deque(['sum', deque(['3', ',', '4', ',', '5'])])]) + assert handcalcs.handcalcs.expr_parser("z = sqrt(5)") == deque( + ["z", "=", deque(["sqrt", "5"])] + ) + assert handcalcs.handcalcs.expr_parser("z = (sqrt(5))") == deque( + ["z", "=", deque(["sqrt", "5"])] + ) + assert handcalcs.handcalcs.expr_parser("z = x**2/sqrt(2)") == deque( + ["z", "=", deque(["x", "**", "2"]), "/", deque(["sqrt", "2"])] + ) + assert handcalcs.handcalcs.expr_parser( + "e1_nu = 1.25e-5 +-1 **2/sum(3,4,5)" + ) == deque( + [ + "e1_nu", + "=", + "1.25e-5", + "+", + deque(["-", deque(["1", "**", "2"])]), + "/", + deque(["sum", deque(["3", ",", "4", ",", "5"])]), + ] + ) + assert handcalcs.handcalcs.expr_parser("e1_nu = -1.25e5 +- 1") == deque( + ["e1_nu", "=", deque(["-", "1.25e5"]), "+", deque(["-", "1"])] + ) + assert handcalcs.handcalcs.expr_parser("e1_nu = kN.to('ksf')") == deque( + ["e1_nu", "=", "kN"] + ) + assert handcalcs.handcalcs.expr_parser( + "e1_nu = 1.25e5+1.25e-5j **(a/b**2)/sum(3,4,5)" + ) == deque( + [ + "e1_nu", + "=", + deque( + ["1.25e5+1.25e-5j", "**", deque(["a", "/", deque(["b", "**", "2"])])] + ), + "/", + deque(["sum", deque(["3", ",", "4", ",", "5"])]), + ] + ) + def test_swap_prime_notation(): - assert handcalcs.handcalcs.swap_prime_notation(deque(["sin", deque(["tan", deque(["a", "/", 4])])])) == deque(["sin", deque(["tan", deque(["a", "/", 4])])]) - assert handcalcs.handcalcs.swap_prime_notation(deque(["f_prime_c", "=" "eta_prime_prime_c", "*", deque(["phi_prime_prime_prime_c", "+", "zeta_pr"])])) == deque(["f'_c", "=" "eta''_c", "*", deque(["phi'''_c", "+", "zeta_pr"])]) \ No newline at end of file + assert handcalcs.handcalcs.swap_prime_notation( + deque(["sin", deque(["tan", deque(["a", "/", 4])])]) + ) == deque(["sin", deque(["tan", deque(["a", "/", 4])])]) + assert handcalcs.handcalcs.swap_prime_notation( + deque( + [ + "f_prime_c", + "=" "eta_prime_prime_c", + "*", + deque(["phi_prime_prime_prime_c", "+", "zeta_pr"]), + ] + ) + ) == deque(["f'_c", "=" "eta''_c", "*", deque(["phi'''_c", "+", "zeta_pr"])]) + diff --git a/test_handcalcs/test_render_file.py b/test_handcalcs/test_render_file.py index 8992a2a..5526442 100644 --- a/test_handcalcs/test_render_file.py +++ b/test_handcalcs/test_render_file.py @@ -11,28 +11,46 @@ from IPython.utils.io import capture_output from IPython.display import Latex + @pytest.fixture(scope="session") def session_ip(): return start_ipython() + @pytest.fixture(scope="function") def ip(session_ip): # yield session_ip session_ip.run_line_magic(magic_name="load_ext", line="handcalcs.render") yield session_ip - #session_ip.run_line_magic(magic_name="unload_ext", line="handcalcs.render") + # session_ip.run_line_magic(magic_name="unload_ext", line="handcalcs.render") session_ip.run_line_magic(magic_name="reset", line="-f") + def test_render(ip): output = ip.run_cell_magic(magic_name="render", line="_testing", cell="x = 99") assert output == "\\[\n\\begin{aligned}\nx &= 99\\;\n\\end{aligned}\n\\]" + def test_tex(ip): output = ip.run_cell_magic(magic_name="tex", line="_testing", cell="x = 99") assert output == "\\[\n\\begin{aligned}\nx &= 99\\;\n\\end{aligned}\n\\]" + def test_parse_line_args(): - assert handcalcs.render.parse_line_args("params 5") == {"override": "params", "precision": 5} - assert handcalcs.render.parse_line_args("symbolic") == {"override": "symbolic", "precision": ""} - assert handcalcs.render.parse_line_args("short long 3") == {"override": "long", "precision": 3} - assert handcalcs.render.parse_line_args("symbolical, 1") == {"override": "", "precision": 1} \ No newline at end of file + assert handcalcs.render.parse_line_args("params 5") == { + "override": "params", + "precision": 5, + } + assert handcalcs.render.parse_line_args("symbolic") == { + "override": "symbolic", + "precision": "", + } + assert handcalcs.render.parse_line_args("short long 3") == { + "override": "long", + "precision": 3, + } + assert handcalcs.render.parse_line_args("symbolical, 1") == { + "override": "", + "precision": 1, + } + diff --git a/test_handcalcs/test_sympy_kit.py b/test_handcalcs/test_sympy_kit.py index ad23e93..e65356e 100644 --- a/test_handcalcs/test_sympy_kit.py +++ b/test_handcalcs/test_sympy_kit.py @@ -9,12 +9,11 @@ a, b = sp.symbols("a b") c = a + b -d = sp.Eq(2*a + b, 14) - +d = sp.Eq(2 * a + b, 14) def test_sympy_cell_line_lists(): - assert sk.sympy_cell_line_lists("x = 9\ny = 10") == [["x","9"], ["y", "10"]] + assert sk.sympy_cell_line_lists("x = 9\ny = 10") == [["x", "9"], ["y", "10"]] def test_test_sympy_parents(): @@ -24,18 +23,29 @@ def test_test_sympy_parents(): def test_test_for_sympy_expr(): assert sk.test_for_sympy_expr("x", {"x": 9, "y": 10}) == False - assert sk.test_for_sympy_expr("c", {"a": a, "b": b, "c": a+b}) == True + assert sk.test_for_sympy_expr("c", {"a": a, "b": b, "c": a + b}) == True + def test_test_for_sympy_eqn(): assert sk.test_for_sympy_eqn("x", {"x": 9, "y": 10}) == False assert sk.test_for_sympy_eqn("d", {"x": 9, "y": 10, "d": d}) == True + def test_get_sympy_object(): assert sk.get_sympy_obj("d", {"x": 9, "y": 10, "d": d}) == d + def test_convert_sympy_cell_to_py_cell(): - assert sk.convert_sympy_cell_to_py_cell("x = 9\ny = 10", {"x": 9, "y": 10}) == "x = 9\ny = 10" - assert sk.convert_sympy_cell_to_py_cell("d", {"x": 9, "y": 10, "d": d}) == "2*a + b=14" - assert sk.convert_sympy_cell_to_py_cell("k = c", {"x": 9, "y": 10, "c": c}) == "k =a + b" + assert ( + sk.convert_sympy_cell_to_py_cell("x = 9\ny = 10", {"x": 9, "y": 10}) + == "x = 9\ny = 10" + ) + assert ( + sk.convert_sympy_cell_to_py_cell("d", {"x": 9, "y": 10, "d": d}) == "2*a + b=14" + ) + assert ( + sk.convert_sympy_cell_to_py_cell("k = c", {"x": 9, "y": 10, "c": c}) + == "k =a + b" + ) with pytest.raises(ValueError): - sk.convert_sympy_cell_to_py_cell("c", {"x": 9, "y": 10, "c": c}) \ No newline at end of file + sk.convert_sympy_cell_to_py_cell("c", {"x": 9, "y": 10, "c": c})