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
118 changes: 118 additions & 0 deletions TYPE_HINTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Type Hints Guide for CALFEM Python

## Overview
Type hints have been added to CALFEM Python to improve code documentation, IDE support, and catch potential type-related bugs early. This is done in a backward-compatible way that doesn't affect runtime behavior.

## Benefits
1. **Better IDE Support**: IDEs can provide better autocomplete, error detection, and refactoring
2. **Documentation**: Function signatures are self-documenting
3. **Error Prevention**: Static type checkers can catch type-related bugs before runtime
4. **Backward Compatibility**: All existing code continues to work unchanged

## Type Hint Patterns

### Basic Types
```python
def func(param: int) -> str:
return str(param)
```

### Array Types
```python
from numpy.typing import ArrayLike, NDArray
import numpy as np

# Input arrays (can be lists, tuples, numpy arrays)
def func(arr: ArrayLike) -> NDArray[np.floating]:
return np.array(arr, dtype=float)
```

### Optional Parameters
```python
from typing import Optional

def func(required: ArrayLike, optional: Optional[ArrayLike] = None) -> NDArray[np.floating]:
if optional is None:
return np.array(required)
return np.array(required) + np.array(optional)
```

### Functions with Conditional Returns
```python
from typing import Union, Tuple

# Returns either Ke alone or (Ke, fe) tuple
def element_func(ep: ArrayLike, eq: Optional[ArrayLike] = None) -> Union[NDArray[np.floating], Tuple[NDArray[np.floating], NDArray[np.floating]]]:
Ke = compute_stiffness(ep)
if eq is None:
return Ke
else:
fe = compute_load(eq)
return Ke, fe
```

### Sparse Matrix Support
```python
from scipy.sparse import csr_matrix, csc_matrix, lil_matrix

def assem_func(K: Union[NDArray[np.floating], csr_matrix, csc_matrix, lil_matrix],
Ke: ArrayLike) -> Union[NDArray[np.floating], csr_matrix, csc_matrix, lil_matrix]:
# Function body
pass
```

## Common CALFEM Type Patterns

### Element Stiffness Functions
- Input: `ex: ArrayLike, ey: ArrayLike, ep: ArrayLike, eq: Optional[ArrayLike] = None`
- Return: `Union[NDArray[np.floating], Tuple[NDArray[np.floating], NDArray[np.floating]]]`

### Element Stress/Force Functions
- Input: `ex: ArrayLike, ey: ArrayLike, ep: ArrayLike, ed: ArrayLike`
- Return: `NDArray[np.floating]` or specific types like `float`

### Utility Functions
- Coordinate functions: `(int, int) -> NDArray[np.integer]`
- Warning/Error functions: `str -> None`

## Type Checking
Install mypy for static type checking:
```bash
pip install mypy
```

Run type checking:
```bash
mypy src/calfem/core.py
```

The mypy.ini configuration allows gradual typing adoption without breaking existing code.

## Implementation Strategy
1. Start with the most commonly used functions
2. Add type hints to new functions as they're written
3. Gradually add hints to existing functions during maintenance
4. Focus on public API functions first
5. Use `# type: ignore` comments for complex cases that mypy can't handle

## Examples in Core Module
The following functions already have type hints as examples:
- `spring1e`: Simple function with array input/output
- `spring1s`: Function returning scalar
- `bar1e`: Function with optional parameters and conditional return
- `beam2e`: Complex element function pattern
- `eigen`: Functions with multiple array inputs/outputs
- `assem`: Functions supporting both dense and sparse matrices
- `create_dofs`: Utility function with integer types

## Future Enhancements
- Add type hints to other modules (mesh, vis, etc.)
- Use Protocol types for more complex interfaces
- Add runtime type checking with libraries like pydantic
- Integration with documentation generation tools

## Compatibility
- Minimum Python version: 3.8 (supports all required typing features)
- No runtime impact: Type hints are ignored at runtime
- All existing code continues to work without modification
- Optional adoption: Teams can choose their level of type hint usage
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@

# General information about the project.
project = 'CALFEM for Python'
copyright = '2016-2024, Jonas Lindemann'
copyright = '2016-2025, Jonas Lindemann et al'
author = 'Jonas Lindemann et al'

# The version info for the project you're documenting, acts as replacement for
Expand Down
2 changes: 1 addition & 1 deletion examples/exm_flow_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
# ----- Solve equation system ------------------------------------

a, r = cfc.solveq(K, f, bc, bcVal)
ed = cfc.extractEldisp(edof, a)
ed = cfc.extract_eldisp(edof, a)

# ----- Calculating element forces -------------------------------

Expand Down
2 changes: 1 addition & 1 deletion examples/exm_qt_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import sys

from calfem.qt5 import *
from qtpy import *

import calfem.core as cfc
import calfem.vis as cfv
Expand Down
242 changes: 242 additions & 0 deletions examples/exm_stress_2d_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# -*- coding: utf-8 -*-

"""Example 06

Solves a plane stress 2D problem using a structured mesh.
Shows how to draw von Mises effective stress as an element value with
drawElementValues(). Shows use of GmshMesher attribute 'nodesOnCurve'
(dictionary that says which nodes are on a given geometry curve)
"""

import calfem.geometry as cfg
import calfem.mesh as cfm
import calfem.vis_mpl as cfv
import calfem.utils as cfu
import calfem.core as cfc
import numpy as np

from math import sqrt
import sys

cfu.enableLogging()

# ---- Define problem variables ---------------------------------------------

t = 0.2
v = 0.35
E = 2.1e9
ptype = 1
ep = [ptype, t]
D = cfc.hooke(ptype, E, v)

# ---- Define geometry ------------------------------------------------------

cfu.info("Creating geometry...")

g = cfg.geometry()

# Just a shorthand. We use this to make the circle arcs.

s2 = 1 / sqrt(2)

points = [
[0, 3],
[3, 3],
[4 - s2, 3 - s2],
[4, 2], # 0-3
[4 + s2, 3 - s2],
[5, 3],
[8, 3],
[0, 0], # 9-13
[3, 0],
[4 - s2, s2],
[4, 1],
[4 + s2, s2], # 14-18
[5, 0],
[8, 0],
[4, 3],
[4, 0]
]

for xp, yp in points:
g.point([xp * 0.1, yp * 0.1])




lines = [
[0, 1], # 0-1
[5, 6],
[6, 13],
[13, 12], # 4-7
[8, 7],
[7, 0],
]

for s in lines:
g.line(s)

# Points in circle arcs are [start, center, end]

circle_arcs = [
[1, 14, 2],
[2, 14, 3],
[3, 14, 4],
[4, 14, 5],
[12, 15, 11],
[11, 15, 10],
[10, 15, 9],
[9, 15, 8]
]

for c in circle_arcs:
g.circle(c)

# Create surfaces from the lines and arcs

g.surface([0, 6, 7, 8, 9, 1, 2, 3, 10, 11, 12, 13, 4, 5])

# Create markers

right_side = 20
left_side = 30

g.line_marker(2, right_side) # Right side
g.line_marker(5, left_side) # Right side

# ---- Create mesh ----------------------------------------------------------

cfu.info("Meshing geometry...")

# Create mesh

mesh = cfm.GmshMesh(geometry=g, return_boundary_elements=True)
mesh.el_type = 3
mesh.dofs_per_node = 2
mesh.el_size_factor = 0.01

coords, edof, dofs, bdofs, element_markers, b_elements = mesh.create()

# ---- Solve problem --------------------------------------------------------

cfu.info("Assembling system matrix...")

nDofs = np.size(dofs)
ex, ey = cfc.coordxtr(edof, coords, dofs)
K = np.zeros([nDofs, nDofs])

for eltopo, elx, ely in zip(edof, ex, ey):
Ke = cfc.planqe(elx, ely, ep, D)
cfc.assem(eltopo, K, Ke)

cfu.info("Solving equation system...")

f = np.zeros([nDofs, 1])

bc = np.array([], "i")
bc_val = np.array([], "f")

bc, bc_val = cfu.apply_bc(bdofs, bc, bc_val, left_side, 0.0, 0)

#cfu.apply_force(bdofs, f, right_side, 10e5, 1)
# boundaryElements, coords, dofs, F, marker, q
cfu.apply_traction_linear_element(
b_elements, coords, dofs, f, right_side, [1e5, 0.0]
)


a, r = cfc.solveq(K, f, bc, bc_val)

cfu.info("Computing element forces...")

ed = cfc.extract_eldisp(edof, a)
von_mises = np.zeros((edof.shape[0]))

stress_table = np.zeros((edof.shape[0], 3))

# For each element:

for i in range(edof.shape[0]):
# Determine element stresses and strains in the element.

es, et = cfc.planqs(ex[i, :], ey[i, :], ep, D, ed[i, :])

# Calc and append effective stress to list

von_mises[i] = sqrt(pow(es[0], 2) - es[0] * es[1] + pow(es[1], 2) + 3 * es[2])

stress_table[i, :] = es

# ---- Tabulate results -----------------------------------------------------

cfu.disp_array(stress_table, ["sigx", "sigy", "tauxy"])

# ---- Visualise results ----------------------------------------------------

cfu.info("Visualising...")

cfv.figure()
cfv.draw_geometry(
g,
draw_points=True,
label_curves=True,
label_points=True,
title="Example 6a - Geometry",
)

cfv.figure()
cfv.draw_mesh(
coords,
edof,
dofs_per_node=mesh.dofs_per_node,
el_type=mesh.el_type,
title="Example 6b - Meshing",
)

cfv.figure()
cfv.draw_element_values(
von_mises,
coords,
edof,
mesh.dofs_per_node,
mesh.el_type,
None,
draw_elements=False,
draw_undisplaced_mesh=False,
title="Example 6c - Effective stress",
)

cfv.figure()
cfv.draw_displacements(
a,
coords,
edof,
mesh.dofs_per_node,
mesh.el_type,
draw_undisplaced_mesh=True,
title="Example 6d - Displacements",
)

# Make use of attribute 'nodesOnCurve' in GmshMesher to draw some arrows on
# the right hand side of the mesh: --> currently not working

# rightSideNodes = set()

# 4 and 5 are the IDs of the curves where we applied the forces.

# for curveID in [4, 5]:
# # Get the nodes, without duplicates.
# rightSideNodes = rightSideNodes.union(set(mesh.nodesOnCurve[curveID]))

# for i in rightSideNodes:
# # Position of the node with displacements.
# x = coords[i, 0] + a[i * 2, 0]
# y = coords[i, 1] + a[i * 2 + 1, 0]

# # A poor man's force indicator. Could also use vv.plot()
# cfv.add_text(r"$\rightarrow$", (x, y), color="g")

# Enter main loop
cfv.show_and_wait()

print("Done.")
2 changes: 1 addition & 1 deletion examples/exm_stress_2d_materials.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@

von_mises.append(
math.sqrt(
pow(es[0], 2) - es[0] * es[1] + pow(es[1], 2) + 3 * pow(es[2], 2)
pow(es[0], 2) - es[0] * es[1] + pow(es[1], 2) + 3 * pow(es[2], 2)
)
)

Expand Down
Loading