Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,47 @@ e.g.
handcalcs.set_option("custom_symbols", {"V_dot": "\\dot{V}", "N_star": "N^{*}"})
```

Will now allow this kind of rendering:
This can also be used to swap substrings or individual characters within the variable name. For example, the following:

```python
handcalcs.set_option("custom_symbols", {"star": "^*", "C": ","})
```
Would render `Mstar_1C2` to $M^*_{1,2}$

This now allow this kind of rendering:

![Custom symbols example showing the use of V_dot and N_star](docs/images/custom_symbols.png)

The docstring in the `handcalcs.set_option()` function demonstrates which options are available and what values they take.
---

#### Custom Brackets (New in v1.?.?)

Functioning similiar to the Custom Symbols, this allows a specified character or string of characters to be swapped for brackets. For example:
```python
handcalcs.set_option("custom_brackets", {
"parenthesis": "ˉ", # macron (ˉ) → parentheses ( )
"square_brackets": "ˍ", # low line (ˍ) → square brackets [ ]
"angle_brackets": "ˆ", # modifier letter circumflex accent (ˆ) → angle brackets ⟨ ⟩
"curly_brackets": "ǂ", # double pipe (ǂ) → curly brackets { }
"pipes": "ǀ", # vertical bar (ǀ) → pipes | |
"double_pipes": "ǁ", # double vertical bar (ǁ) → double pipes ‖ ‖
})
```
Will render the following:<br>
`myvarˉ1ˉ` -> $myvar(1)$<br>
`myvarˍ2ˍ` -> $myvar[2]$<br>
`myvarˆ3ˆ` -> $myvar\langle3\rangle$<br>
`myvarǂ4ǂ` -> $myvar\lbrace4\rbrace$<br>
`myvarǀ5ǀ` -> $myvar|5|$<br>
`myvarǁ6ǁ` -> $myvar\|6\|$<br>
These can be nested as below:<br>
`myvarˉˆ7ˆˉ_ǁ8ǁ` -> $myvar(\langle7\rangle)_{\|8\|}$<br>

Note that the example above utilizes a range of rarely used characters for the mapping, which has the drawback of making the variable names not readily typed on the keyboard. The user could alternatively specify `"square_brackets": "SB"` for mapping to square brackets, for example, such that `myvarSB9SB` would map to $myvar[9]$, but this then makes the variable names less legible.

Note that as valid LaTeX strings require paired brackets, there will always be an equal number of opening and closing brackets, so the function works by sequentially replacing opening and closing brackets. This means you can't create `myvar(1(2))` as this would always map to `myvar(1)(2)`


## Override tags

Expand Down
3 changes: 2 additions & 1 deletion src/handcalcs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
"greek_exclusions": [],
"param_columns": 3,
"preferred_string_formatter": "L",
"custom_symbols": {}
"custom_symbols": {},
"custom_brackets": {}
}
116 changes: 109 additions & 7 deletions src/handcalcs/handcalcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2051,6 +2051,7 @@ def swap_symbolic_calcs(
functions_on_symbolic_expressions = [
insert_parentheses,
swap_custom_symbols,
swap_custom_brackets,
swap_math_funcs,
swap_superscripts,
swap_chained_fracs,
Expand Down Expand Up @@ -2161,15 +2162,117 @@ def swap_custom_symbols(d: deque, **config_options) -> deque:
custom_symbols = config_options.get("custom_symbols", {})
new_item = item
for symbol, latex_symbol in custom_symbols.items():
if symbol in item:
new_item = item.replace(symbol, latex_symbol)
break
if (
symbol in new_item
): # Changed to new_item to allow changes to a string accumulate
new_item = new_item.replace(
symbol, latex_symbol
) # Changed to new_item to allow changes to a string accumulate
# Removed break to permit multiple replacements
swapped_items.append(new_item)
else:
swapped_items.append(item)
return swapped_items


def swap_custom_brackets(d: deque, **config_options) -> deque:
"""
Swaps custom bracket character or string with their corresponding LaTeX brackets.
Set with user defined dictionary 'custom_brackets' in config_options

Example usage:

handcalcs.set_option("custom_brackets", {
"parenthesis": 'ˉ', # Replace ˉ with consecutively alternating ( and ) characters
"square_brackets": 'ˍ' # Replaces ˍ with consecutively alternating [ and ] characters
})

Resulting behavior:
'myvarˉ0ˉ' -> 'myvar(0)'
'myvarˍ0ˍ' -> 'myvar[0]'

Supported bracket types:
- parenthesis
- square_brackets
- angle_brackets
- curly_brackets
- pipes
- double_pipes
"""
swapped_items = deque([])
for item in d:
if isinstance(item, deque):
new_item = swap_custom_brackets(item, **config_options)
swapped_items.append(new_item)
elif isinstance(item, str):
custom_brackets = config_options.get("custom_brackets", {})
new_item = item

# Process each bracket type
for bracket_type, custom_str in custom_brackets.items():
if custom_str and custom_str in new_item:
if bracket_type == "parenthesis":
new_item = _replace_alternating_brackets(
new_item, custom_str, "(", ")"
)
elif bracket_type == "square_brackets":
new_item = _replace_alternating_brackets(
new_item, custom_str, "[", "]"
)
elif bracket_type == "angle_brackets":
new_item = _replace_alternating_brackets(
new_item, custom_str, r"\langle", r"\rangle"
)
elif bracket_type == "curly_brackets":
new_item = _replace_alternating_brackets(
new_item, custom_str, r"\lbrace", r"\rbrace"
)
elif bracket_type == "pipes":
new_item = _replace_alternating_brackets(
new_item, custom_str, "|", "|"
)
elif bracket_type == "double_pipes":
new_item = _replace_alternating_brackets(
new_item, custom_str, r"\|", r"\|"
)

swapped_items.append(new_item)
else:
swapped_items.append(item)
return swapped_items


def _replace_alternating_brackets(
text: str, custom_str: str, left_bracket: str, right_bracket: str
) -> str:
"""
Helper function to replace alternating occurrences of custom_str with left and right brackets.
Supports both single and multi-character custom_str strings.
"""
if custom_str not in text:
return text

result = ""
is_left = True # Start with left bracket
i = 0
str_len = len(custom_str)

while i < len(text):
# Check if we're at the start of a custom_str occurrence
if text[i : i + str_len] == custom_str:
if is_left:
result += left_bracket
else:
result += right_bracket
is_left = not is_left # Toggle for next occurrence
i += str_len # Skip past the entire custom_str
else:
result += text[i]
i += 1

return result


def swap_log_func(d: deque, calc_results: dict, **config_options) -> deque:
"""
Returns a new deque representing 'd' but with any log functions swapped
Expand Down Expand Up @@ -2397,21 +2500,20 @@ def extend_subscripts(pycode_as_deque: deque, **config_options) -> deque:
"""
swapped_deque = deque([])
for item in pycode_as_deque:
discount = 0 # hack to prevent excess braces from swap_long_var_str
if isinstance(item, deque):
new_item = extend_subscripts(item) # recursion!
swapped_deque.append(new_item)
elif isinstance(item, str) and "_" in item and not "\\int" in item:
if "\\mathrm{" in item or "\\operatorname{" in item:
discount = 1
new_item = ""
for char in item:
if char == "_":
new_item += char
new_item += "{"
else:
new_item += char
num_braces = new_item.count("{") - discount
num_braces = new_item.count("{") - new_item.count(
"}"
) # count unclosed braces

new_item += "}" * num_braces
swapped_deque.append(new_item)
Expand Down
30 changes: 30 additions & 0 deletions test_handcalcs/test_handcalcs_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@
"param_columns": 3,
"preferred_string_formatter": "L",
"custom_symbols": {"V_dot": "\\dot{V}"},
"custom_brackets": {
"parenthesis": "ˉ",
"square_brackets": "ˍ",
"angle_brackets": "ˆ",
"curly_brackets": "ǂ",
"pipes": "ǀ",
"double_pipes": "ǁ",
}
}

config_underscore_spaces = config_options.copy()
Expand Down Expand Up @@ -1289,6 +1297,28 @@ def test_swap_for_greek():
deque(["lamb", "=", 3]), **config_options
) == deque(["\\lambda", "=", 3])

def test_swap_custom_brackets():
assert handcalcs.handcalcs.swap_custom_brackets(
deque(["myvarˉ1ˉ", "=", "1"]), **config_options # Test round brackets
) == deque(["myvar(1)", "=", "1"])
assert handcalcs.handcalcs.swap_custom_brackets(
deque(["myvarˍ2ˍ", "=", "1"]), **config_options # Test square brackets
) == deque(["myvar[2]", "=", "1"])
assert handcalcs.handcalcs.swap_custom_brackets(
deque(["myvarˆ3ˆ", "=", "1"]), **config_options # Test angle brackets
) == deque([r"myvar\langle3\rangle", "=", "1"])
assert handcalcs.handcalcs.swap_custom_brackets(
deque(["myvarǂ4ǂ", "=", "1"]), **config_options # Test curly brackets
) == deque([r"myvar\lbrace4\rbrace", "=", "1"])
assert handcalcs.handcalcs.swap_custom_brackets(
deque(["myvarǀ5ǀ", "=", "1"]), **config_options # Test pipes
) == deque(["myvar|5|", "=", "1"])
assert handcalcs.handcalcs.swap_custom_brackets(
deque(["myvarǁ6ǁ", "=", "1"]), **config_options # Test double pipes
) == deque([r"myvar\|6\|", "=", "1"])
assert handcalcs.handcalcs.swap_custom_brackets(
deque(["myvarˉˆ7ˆˉ_ǁAǁ", "=", "1"]), **config_options # Test mixed brackets
) == deque([r"myvar(\langle7\rangle)_\|A\|", "=", "1"])

def test_test_for_scientific_float():
assert handcalcs.handcalcs.test_for_scientific_float("1.233e-3") == True
Expand Down