PyNiteFEA 2.0.2__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {pynitefea-2.0.2/PyNiteFEA.egg-info → pynitefea-2.1.0}/PKG-INFO +22 -3
  2. pynitefea-2.0.2/README.md → pynitefea-2.1.0/PyNiteFEA.egg-info/PKG-INFO +69 -0
  3. {pynitefea-2.0.2 → pynitefea-2.1.0}/PyNiteFEA.egg-info/requires.txt +1 -1
  4. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Analysis.py +41 -36
  5. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/FEModel3D.py +418 -504
  6. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Member3D.py +69 -69
  7. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Mesh.py +78 -0
  8. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Rendering.py +201 -155
  9. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/ShearWall.py +4 -0
  10. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Visualization.py +226 -112
  11. pynitefea-2.1.0/Pynite/__init__.py +12 -0
  12. pynitefea-2.0.2/PKG-INFO → pynitefea-2.1.0/README.md +16 -50
  13. {pynitefea-2.0.2 → pynitefea-2.1.0}/setup.py +6 -3
  14. pynitefea-2.0.2/Pynite/__init__.py +0 -12
  15. {pynitefea-2.0.2 → pynitefea-2.1.0}/LICENSE +0 -0
  16. {pynitefea-2.0.2 → pynitefea-2.1.0}/PyNiteFEA.egg-info/SOURCES.txt +0 -0
  17. {pynitefea-2.0.2 → pynitefea-2.1.0}/PyNiteFEA.egg-info/dependency_links.txt +0 -0
  18. {pynitefea-2.0.2 → pynitefea-2.1.0}/PyNiteFEA.egg-info/top_level.txt +0 -0
  19. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/BeamSegY.py +0 -0
  20. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/BeamSegZ.py +0 -0
  21. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/FixedEndReactions.py +0 -0
  22. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/LoadCombo.py +0 -0
  23. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/MainStyleSheet.css +0 -0
  24. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/MatFoundation.py +0 -0
  25. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Material.py +0 -0
  26. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Node3D.py +0 -0
  27. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/PhysMember.py +0 -0
  28. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Plate3D.py +0 -0
  29. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Quad3D.py +0 -0
  30. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Report_Template.html +0 -0
  31. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Reporting.py +0 -0
  32. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Section.py +0 -0
  33. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Spring3D.py +0 -0
  34. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/Tri3D.py +0 -0
  35. {pynitefea-2.0.2 → pynitefea-2.1.0}/Pynite/VTKWriter.py +0 -0
  36. {pynitefea-2.0.2 → pynitefea-2.1.0}/setup.cfg +0 -0
@@ -1,17 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyNiteFEA
3
- Version: 2.0.2
3
+ Version: 2.1.0
4
4
  Summary: A simple elastic 3D structural finite element library for Python.
5
5
  Home-page: https://github.com/JWock82/Pynite.git
6
6
  Author: D. Craig Brinck, PE, SE
7
7
  Author-email: Building.Code@outlook.com
8
8
  Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
9
12
  Classifier: License :: OSI Approved :: MIT License
10
13
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.10
14
+ Requires-Python: >=3.11
12
15
  Description-Content-Type: text/markdown
13
16
  License-File: LICENSE
14
- Requires-Dist: numpy
17
+ Requires-Dist: numpy>=2.4.0
15
18
  Requires-Dist: PrettyTable
16
19
  Requires-Dist: scipy
17
20
  Requires-Dist: matplotlib
@@ -56,6 +59,7 @@ Dynamic: summary
56
59
 
57
60
  ![Build Status](https://github.com/JWock82/Pynite/actions/workflows/build-and-test.yml/badge.svg)
58
61
  [![codecov](https://codecov.io/gh/JWock82/Pynite/branch/main/graph/badge.svg?token=ZH18US3A7P)](https://codecov.io/gh/JWock82/Pynite)
62
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/PyniteFEA)
59
63
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyNiteFEA?cacheSeconds=86400)
60
64
  <img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/JWock82/Pynite">
61
65
  ![GitHub last commit](https://img.shields.io/github/last-commit/JWock82/Pynite)
@@ -117,10 +121,25 @@ Here's a list of projects that use Pynite:
117
121
  * Phaenotyp (https://github.com/bewegende-Architektur/Phaenotyp) (https://youtu.be/shloSw9HjVI)
118
122
 
119
123
  # What's New?
124
+ v2.1.0
125
+ * Major speed boost and leaner memory usage: the global stiffness/mass assembly plus the nodal load and fixed-end reaction builders now use block-based vectorized writes instead of Python loops, which keeps data in contiguous NumPy buffers rather than thousands of temporary Python objects. The dense solver sees ~15-25% faster `K`/`M` builds in our targeted benchmarks, while sparse solver runs see ~30% faster stiffness assembly in our benchmarks.
126
+ * Meshes can now be regenerated with the `FEModel3D.meshes[mesh_name].generate()` command. This command removes the old mesh and replaces it with an updated one. The mesh will not remove nodes needed by other elements outside the mesh. Note that any loads applied to the old mesh will be lost when its elements and nodes are deleted. This makes meshes truly parametric.
127
+ * Added `FEModel3D.delete_mesh()` to help deleting old meshes properly.
128
+ * Renderers now automatically set the annotation size as 5% of the minimum distance between nodes. You still have the option of overiding this by setting the annotation size manually.
129
+ * Added load case/combo identification to VTK rendering.
130
+ * Dropped support for Python version 3.10, which is not compatible with Numpy 2.4.
131
+ * Removed unecessary checks for Scipy. Scipy has been a required dependency for some time now.
132
+ * Cleaned up whitespace to conform to PEP8 style guide.
133
+
134
+ v2.0.3
135
+ * Numpy >= 2.4 is now a requirement. This ensures the VTK renderer will handle plate results arrays properly.
136
+ * `pip` now installs dependencies during build testing. Pynite's testing suite now runs against the correct version of each dependency.
137
+
120
138
  v2.0.2
121
139
  * Added docstrings to the VTK `Renderer` class to help the user.
122
140
  * Enforced use of properties instead of attributes in the VTK `Renderer` and added docstrings to properties help the user make decisions.
123
141
  * Removed the VTK `Renderer`'s properties that began with "set_". These were redundant and caused confusion. The prefix "set_" is no longer used to access or set any of the `Renderer`'s properties.
142
+ * Made the VTK `Renderer` compatible with the latest version of `numpy`.
124
143
 
125
144
  v2.0.1
126
145
  * Pynite no longer struggles rendering load cases and combinations when no loads are present. This is especially helpful for rendering modal load combinations, which don't have loads (only masses).
@@ -1,3 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyNiteFEA
3
+ Version: 2.1.0
4
+ Summary: A simple elastic 3D structural finite element library for Python.
5
+ Home-page: https://github.com/JWock82/Pynite.git
6
+ Author: D. Craig Brinck, PE, SE
7
+ Author-email: Building.Code@outlook.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy>=2.4.0
18
+ Requires-Dist: PrettyTable
19
+ Requires-Dist: scipy
20
+ Requires-Dist: matplotlib
21
+ Provides-Extra: all
22
+ Requires-Dist: Ipython; extra == "all"
23
+ Requires-Dist: vtk>=9.3.0; extra == "all"
24
+ Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "all"
25
+ Requires-Dist: trame_jupyter_extension; extra == "all"
26
+ Requires-Dist: ipywidgets; extra == "all"
27
+ Requires-Dist: pdfkit; extra == "all"
28
+ Requires-Dist: Jinja2; extra == "all"
29
+ Provides-Extra: vtk
30
+ Requires-Dist: IPython; extra == "vtk"
31
+ Requires-Dist: vtk>=9.3.0; extra == "vtk"
32
+ Provides-Extra: pyvista
33
+ Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "pyvista"
34
+ Requires-Dist: trame_jupyter_extension; extra == "pyvista"
35
+ Requires-Dist: ipywidgets; extra == "pyvista"
36
+ Provides-Extra: reporting
37
+ Requires-Dist: pdfkit; extra == "reporting"
38
+ Requires-Dist: Jinja2; extra == "reporting"
39
+ Provides-Extra: derivations
40
+ Requires-Dist: jupyterlab; extra == "derivations"
41
+ Requires-Dist: sympy; extra == "derivations"
42
+ Dynamic: author
43
+ Dynamic: author-email
44
+ Dynamic: classifier
45
+ Dynamic: description
46
+ Dynamic: description-content-type
47
+ Dynamic: home-page
48
+ Dynamic: license-file
49
+ Dynamic: provides-extra
50
+ Dynamic: requires-dist
51
+ Dynamic: requires-python
52
+ Dynamic: summary
53
+
1
54
  <div align="center">
2
55
  <img src="https://github.com/JWock82/Pynite/raw/main/Resources/Full Logo No Buffer.png" width=40% align="center"/>
3
56
  <br>
@@ -6,6 +59,7 @@
6
59
 
7
60
  ![Build Status](https://github.com/JWock82/Pynite/actions/workflows/build-and-test.yml/badge.svg)
8
61
  [![codecov](https://codecov.io/gh/JWock82/Pynite/branch/main/graph/badge.svg?token=ZH18US3A7P)](https://codecov.io/gh/JWock82/Pynite)
62
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/PyniteFEA)
9
63
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyNiteFEA?cacheSeconds=86400)
10
64
  <img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/JWock82/Pynite">
11
65
  ![GitHub last commit](https://img.shields.io/github/last-commit/JWock82/Pynite)
@@ -67,10 +121,25 @@ Here's a list of projects that use Pynite:
67
121
  * Phaenotyp (https://github.com/bewegende-Architektur/Phaenotyp) (https://youtu.be/shloSw9HjVI)
68
122
 
69
123
  # What's New?
124
+ v2.1.0
125
+ * Major speed boost and leaner memory usage: the global stiffness/mass assembly plus the nodal load and fixed-end reaction builders now use block-based vectorized writes instead of Python loops, which keeps data in contiguous NumPy buffers rather than thousands of temporary Python objects. The dense solver sees ~15-25% faster `K`/`M` builds in our targeted benchmarks, while sparse solver runs see ~30% faster stiffness assembly in our benchmarks.
126
+ * Meshes can now be regenerated with the `FEModel3D.meshes[mesh_name].generate()` command. This command removes the old mesh and replaces it with an updated one. The mesh will not remove nodes needed by other elements outside the mesh. Note that any loads applied to the old mesh will be lost when its elements and nodes are deleted. This makes meshes truly parametric.
127
+ * Added `FEModel3D.delete_mesh()` to help deleting old meshes properly.
128
+ * Renderers now automatically set the annotation size as 5% of the minimum distance between nodes. You still have the option of overiding this by setting the annotation size manually.
129
+ * Added load case/combo identification to VTK rendering.
130
+ * Dropped support for Python version 3.10, which is not compatible with Numpy 2.4.
131
+ * Removed unecessary checks for Scipy. Scipy has been a required dependency for some time now.
132
+ * Cleaned up whitespace to conform to PEP8 style guide.
133
+
134
+ v2.0.3
135
+ * Numpy >= 2.4 is now a requirement. This ensures the VTK renderer will handle plate results arrays properly.
136
+ * `pip` now installs dependencies during build testing. Pynite's testing suite now runs against the correct version of each dependency.
137
+
70
138
  v2.0.2
71
139
  * Added docstrings to the VTK `Renderer` class to help the user.
72
140
  * Enforced use of properties instead of attributes in the VTK `Renderer` and added docstrings to properties help the user make decisions.
73
141
  * Removed the VTK `Renderer`'s properties that began with "set_". These were redundant and caused confusion. The prefix "set_" is no longer used to access or set any of the `Renderer`'s properties.
142
+ * Made the VTK `Renderer` compatible with the latest version of `numpy`.
74
143
 
75
144
  v2.0.1
76
145
  * Pynite no longer struggles rendering load cases and combinations when no loads are present. This is especially helpful for rendering modal load combinations, which don't have loads (only masses).
@@ -1,4 +1,4 @@
1
- numpy
1
+ numpy>=2.4.0
2
2
  PrettyTable
3
3
  scipy
4
4
  matplotlib
@@ -48,18 +48,20 @@ def _prepare_model(model: FEModel3D, n_modes: int = 0) -> None:
48
48
  for i in range(n_modes):
49
49
  model.add_load_combo(f'Mode {i + 1}', {}, ['modal'])
50
50
 
51
- # Generate all basic meshes
51
+ # Generate all basic meshes that haven't been generated yet
52
52
  for mesh in model.meshes.values():
53
- if mesh.is_generated == False:
53
+ if not mesh.is_generated:
54
54
  mesh.generate()
55
55
 
56
- # Generate all shear wall meshes
56
+ # Generate all shear wall meshes that haven't been generated yet
57
57
  for shear_wall in model.shear_walls.values():
58
- shear_wall.generate()
58
+ if not shear_wall.is_generated:
59
+ shear_wall.generate()
59
60
 
60
- # Generate all mat foundation meshes
61
+ # Generate all mat foundation meshes that haven't been generated yet
61
62
  for mat in model.mats.values():
62
- mat.generate()
63
+ if not mat.is_generated:
64
+ mat.generate()
63
65
 
64
66
  # Activate all springs and members for all load combinations
65
67
  for spring in model.springs.values():
@@ -217,9 +219,9 @@ def _PDelta(model: FEModel3D, combo_name: str, P1: NDArray[float64], FER1: NDArr
217
219
  # Calculate the partitioned initial stiffness matrices. These matrices must be recalculated on each T/C iteration due to tension/compression-only members deactivating or reactivating.
218
220
  if log:
219
221
  print('- Calculating initial stiffness matrix')
220
- K11, K12, K21, K22 = _partition(model, model.K(combo_name, log, check_stability, sparse).tolil(), D1_indices, D2_indices)
222
+ K11, K12, K21, K22 = _partition(model, model.K(combo_name, log, check_stability, sparse), D1_indices, D2_indices)
221
223
 
222
- # The initial stiffness matrices are currently `lil` format which is great for memory, but slow for mathematical operations. They will be converted to `csr` format.
224
+ # The initial stiffness matrices are in `coo` format from construction. They will be converted to `csr` format for efficient mathematical operations.
223
225
  K11 = K11.tocsr()
224
226
  K12 = K12.tocsr()
225
227
  K21 = K21.tocsr()
@@ -232,7 +234,7 @@ def _PDelta(model: FEModel3D, combo_name: str, P1: NDArray[float64], FER1: NDArr
232
234
  if log: print('- Calculating geometric stiffness matrix')
233
235
  Kg11, Kg12, Kg21, Kg22 = _partition(model, model.Kg(combo_name, log, sparse, False), D1_indices, D2_indices)
234
236
 
235
- # The Kg stiffness matrices are currently `lil` format which is great for memory, but slow for mathematical operations. They will be converted to `csr` format. Note that the `+` operator performs matrix addition on `csr` matrices.
237
+ # The Kg stiffness matrices are in `coo` format from construction. They will be converted to `csr` format for efficient addition. Note that the `+` operator performs matrix addition on `csr` matrices.
236
238
  if log: print('- Summing initial & geometric stiffness matrices')
237
239
  K11 = K11 + Kg11.tocsr()
238
240
  K12 = K12 + Kg12.tocsr()
@@ -325,21 +327,20 @@ def _pushover_step(model: FEModel3D, combo_name: str, push_combo: str, step_num:
325
327
 
326
328
  # Calculate the initial stiffness matrix
327
329
  if log: print('- Calculating elastic stiffness matrix [Ke]')
328
- K11, K12, K21, K22 = _partition(model, model.K(combo_name, log, check_stability, sparse).tolil(), D1_indices, D2_indices)
330
+ K11, K12, K21, K22 = _partition(model, model.K(combo_name, log, check_stability, sparse), D1_indices, D2_indices)
329
331
 
330
332
  # Calculate the geometric stiffness matrix
331
333
  # The `combo_name` variable in the code below is not the name of the pushover load combination. Rather it is the name of the primary combination that the pushover load will be added to. Axial loads used to develop Kg are calculated from the displacements stored in `combo_name`.
332
334
  if log: print('- Calculating geometric stiffness matrix [Kg]')
333
- Kg11, Kg12, Kg21, Kg22 = _partition(model, model.Kg(combo_name, log, sparse, False).tolil(), D1_indices, D2_indices)
335
+ Kg11, Kg12, Kg21, Kg22 = _partition(model, model.Kg(combo_name, log, sparse, False), D1_indices, D2_indices)
334
336
 
335
337
  # Calculate the stiffness reduction matrix
336
338
  if log: print('- Calculating plastic reduction matrix [Km]')
337
- Km11, Km12, Km21, Km22 = _partition(model, model.Km(combo_name, push_combo, step_num, log, sparse).tolil(), D1_indices, D2_indices)
339
+ Km11, Km12, Km21, Km22 = _partition(model, model.Km(combo_name, push_combo, step_num, log, sparse), D1_indices, D2_indices)
338
340
 
339
- # The stiffness matrices are currently `lil` format which is great for
340
- # memory, but slow for mathematical operations. They will be converted to
341
- # `csr` format. The `+` operator performs matrix addition on `csr`
342
- # matrices.
341
+ # The stiffness matrices are in `coo` format from construction. They will be
342
+ # converted to `csr` format for efficient operations. The `+` operator
343
+ # performs matrix addition on `csr` matrices.
343
344
  K11 = K11.tocsr() + Kg11.tocsr() + Km11.tocsr()
344
345
  K12 = K12.tocsr() + Kg12.tocsr() + Km12.tocsr()
345
346
  K21 = K21.tocsr() + Kg21.tocsr() + Km21.tocsr()
@@ -718,7 +719,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
718
719
  node.RxnMZ[combo.name] = 0.0
719
720
 
720
721
  # Determine if the node has any supports
721
- if (node.support_DX or node.support_DY or node.support_DZ
722
+ if (node.support_DX or node.support_DY or node.support_DZ
722
723
  or node.support_RX or node.support_RY or node.support_RZ):
723
724
 
724
725
  # Sum the spring end forces at the node
@@ -727,7 +728,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
727
728
  if spring.i_node == node and spring.active[combo.name] == True:
728
729
 
729
730
  # Get the spring's global force matrix
730
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
731
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
731
732
  spring_F = spring.F(combo.name)
732
733
 
733
734
  if node.support_DX: node.RxnFX[combo.name] += spring_F[0, 0]
@@ -740,7 +741,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
740
741
  elif spring.j_node == node and spring.active[combo.name] == True:
741
742
 
742
743
  # Get the spring's global force matrix
743
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
744
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
744
745
  spring_F = spring.F(combo.name)
745
746
 
746
747
  if node.support_DX: node.RxnFX[combo.name] += spring_F[6, 0]
@@ -759,7 +760,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
759
760
  if member.i_node == node and phys_member.active[combo.name] == True:
760
761
 
761
762
  # Get the member's global force matrix
762
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
763
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
763
764
  member_F = member.F(combo.name)
764
765
 
765
766
  if node.support_DX: node.RxnFX[combo.name] += member_F[0, 0]
@@ -772,7 +773,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
772
773
  elif member.j_node == node and phys_member.active[combo.name] == True:
773
774
 
774
775
  # Get the member's global force matrix
775
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
776
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
776
777
  member_F = member.F(combo.name)
777
778
 
778
779
  if node.support_DX: node.RxnFX[combo.name] += member_F[6, 0]
@@ -788,7 +789,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
788
789
  if plate.i_node == node:
789
790
 
790
791
  # Get the plate's global force matrix
791
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
792
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
792
793
  plate_F = plate.F(combo.name)
793
794
 
794
795
  if node.support_DX: node.RxnFX[combo.name] += plate_F[0, 0]
@@ -801,7 +802,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
801
802
  elif plate.j_node == node:
802
803
 
803
804
  # Get the plate's global force matrix
804
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
805
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
805
806
  plate_F = plate.F(combo.name)
806
807
 
807
808
  if node.support_DX: node.RxnFX[combo.name] += plate_F[6, 0]
@@ -814,7 +815,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
814
815
  elif plate.m_node == node:
815
816
 
816
817
  # Get the plate's global force matrix
817
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
818
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
818
819
  plate_F = plate.F(combo.name)
819
820
 
820
821
  if node.support_DX: node.RxnFX[combo.name] += plate_F[12, 0]
@@ -827,7 +828,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
827
828
  elif plate.n_node == node:
828
829
 
829
830
  # Get the plate's global force matrix
830
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
831
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
831
832
  plate_F = plate.F(combo.name)
832
833
 
833
834
  if node.support_DX: node.RxnFX[combo.name] += plate_F[18, 0]
@@ -843,7 +844,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
843
844
  if quad.i_node == node:
844
845
 
845
846
  # Get the quad's global force matrix
846
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
847
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
847
848
  quad_F = quad.F(combo.name)
848
849
 
849
850
  if node.support_DX: node.RxnFX[combo.name] += quad_F[0, 0]
@@ -856,7 +857,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
856
857
  elif quad.j_node == node:
857
858
 
858
859
  # Get the quad's global force matrix
859
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
860
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
860
861
  quad_F = quad.F(combo.name)
861
862
 
862
863
  if node.support_DX: node.RxnFX[combo.name] += quad_F[6, 0]
@@ -869,7 +870,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
869
870
  elif quad.m_node == node:
870
871
 
871
872
  # Get the quad's global force matrix
872
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
873
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
873
874
  quad_F = quad.F(combo.name)
874
875
 
875
876
  if node.support_DX: node.RxnFX[combo.name] += quad_F[12, 0]
@@ -882,7 +883,7 @@ def _calc_reactions(model: FEModel3D, log: bool = False, combo_tags: List[str] |
882
883
  elif quad.n_node == node:
883
884
 
884
885
  # Get the quad's global force matrix
885
- # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
886
+ # Storing it as a local variable eliminates the need to rebuild it every time a term is needed
886
887
  quad_F = quad.F(combo.name)
887
888
 
888
889
  if node.support_DX: node.RxnFX[combo.name] += quad_F[18, 0]
@@ -1020,7 +1021,7 @@ def _check_statics(model: FEModel3D, combo_tags: List[str] | None = None) -> Non
1020
1021
  SumRFZ += RFZ
1021
1022
  SumRMX += RMX - RFY*Z + RFZ*Y
1022
1023
  SumRMY += RMY + RFX*Z - RFZ*X
1023
- SumRMZ += RMZ - RFX*Y + RFY*X
1024
+ SumRMZ += RMZ - RFX*Y + RFY*X
1024
1025
 
1025
1026
  # Add the results to the table
1026
1027
  statics_table.add_row([combo.name, '{:.3g}'.format(SumFX), '{:.3g}'.format(SumRFX),
@@ -1120,10 +1121,10 @@ def _partition_D(model: FEModel3D) -> Tuple[List[int], List[int], NDArray[float6
1120
1121
  else:
1121
1122
  D2_indices.append(node.ID*6 + 5)
1122
1123
  D2.append(0.0)
1123
-
1124
+
1124
1125
  # Legacy code on the next line. I will leave it here until the line that follows has been proven over time.
1125
1126
  # D2 = atleast_2d(D2)
1126
-
1127
+
1127
1128
  # Convert D2 from a list to a matrix
1128
1129
  D2 = array(D2, ndmin=2).T
1129
1130
 
@@ -1144,6 +1145,10 @@ def _partition(model: FEModel3D, unp_matrix: NDArray[float64] | lil_matrix, D1_i
1144
1145
  :rtype: array, array, array, array
1145
1146
  """
1146
1147
 
1148
+ # Convert sparse matrices to CSR so they support efficient slicing
1149
+ if hasattr(unp_matrix, 'tocsr'):
1150
+ unp_matrix = unp_matrix.tocsr()
1151
+
1147
1152
  # Determine if this is a 1D vector or a 2D matrix
1148
1153
 
1149
1154
  # 1D vectors
@@ -1167,11 +1172,11 @@ def _renumber(model: FEModel3D) -> None:
1167
1172
  Assigns node and element ID numbers to be used internally by the program. Numbers are
1168
1173
  assigned according to the order in which they occur in each dictionary.
1169
1174
  """
1170
-
1175
+
1171
1176
  # Number each node in the model
1172
1177
  for id, node in enumerate(model.nodes.values()):
1173
1178
  node.ID = id
1174
-
1179
+
1175
1180
  # Number each spring in the model
1176
1181
  for id, spring in enumerate(model.springs.values()):
1177
1182
  spring.ID = id
@@ -1183,11 +1188,11 @@ def _renumber(model: FEModel3D) -> None:
1183
1188
  for member in phys_member.sub_members.values():
1184
1189
  member.ID = id
1185
1190
  id += 1
1186
-
1191
+
1187
1192
  # Number each plate in the model
1188
1193
  for id, plate in enumerate(model.plates.values()):
1189
1194
  plate.ID = id
1190
-
1195
+
1191
1196
  # Number each quadrilateral in the model
1192
1197
  for id, quad in enumerate(model.quads.values()):
1193
1198
  quad.ID = id