csfpy 0.2.1__tar.gz → 0.2.3__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.
- {csfpy-0.2.1/src/csfpy.egg-info → csfpy-0.2.3}/PKG-INFO +24 -4
- {csfpy-0.2.1 → csfpy-0.2.3}/README.md +23 -3
- {csfpy-0.2.1 → csfpy-0.2.3}/pyproject.toml +1 -1
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/surface_halfwave.py +49 -47
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/torsion_halfwave.py +72 -50
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/basis_plugins.py +18 -1
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_lagrange.py +2 -125
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/compiled_field.py +148 -5
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/engine.py +22 -21
- {csfpy-0.2.1 → csfpy-0.2.3/src/csfpy.egg-info}/PKG-INFO +24 -4
- {csfpy-0.2.1 → csfpy-0.2.3}/LICENSE +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/setup.cfg +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/CSFActions.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/CSFStacked.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/_tol.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/export_yaml.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_properties.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_section_2d.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_shear_weight.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_volume_3d.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_weight.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/section_area_by_weight.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/section_selected_analysis.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/volume.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/weight_lab_zrelative.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/write_opensees_geometry.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/write_sap2000_geometry.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/continuous_section_field.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/__main__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/output/post.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/bending_torsion_halfwave.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/torsion_uniform.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/uniform_surface.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/case.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/basis.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/closed_form_sectional.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/integration.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/material.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/nucleus.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/section.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/sectional.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/sectional_geometry.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/csf_bridge.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_lagrange_q1.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_legendre.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_maclaurin.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_maclaurin_tensor.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/maclaurin_tensor.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/numerics.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/problem/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/problem/point_bc.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/problem/problem.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/problem/problem_api.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/assembly.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/augmented_solver.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/element.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/linear_constraint.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/longitudinal.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/recovery.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/tools/evaluate_compiled_displacement.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/entities.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/io/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/io/csf_issues.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/io/csf_reader.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/io/csf_rough_validator.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/polygon_stress.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/section_field.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/__init__.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/csf_create_cone.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/csf_polygon_hole_builder_v2.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/csf_sp.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/csf_torsion_fem.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/sp_csf.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/writegeometry_rio_v2.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/writegeometry_tappered_pole.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/visualizer.py +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/SOURCES.txt +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/dependency_links.txt +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/entry_points.txt +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/requires.txt +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/top_level.txt +0 -0
- {csfpy-0.2.1 → csfpy-0.2.3}/tests/test_verification.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csfpy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: An Analytical Engine for Non-Prismatic Structural Members
|
|
5
5
|
Author: Giovanni Boscu
|
|
6
6
|
License: GPL-3.0
|
|
@@ -34,6 +34,7 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|
[](https://doi.org/10.5281/zenodo.20687216)
|
|
36
36
|

|
|
37
|
+

|
|
37
38
|

|
|
38
39
|

|
|
39
40
|

|
|
@@ -56,9 +57,8 @@ It represents variable geometry and material distribution as continuous function
|
|
|
56
57
|
The resulting data can be exported in **CSV** and **YAML** formats for integration with beam solvers such as OpenSees and SAP2000, or with section-analysis tools such as [csf_sp](https://github.com/giovanniboscu/continuous-section-field/blob/main/docs/csf_sp_user_guide.md) and [sp_csf](https://github.com/giovanniboscu/continuous-section-field/blob/main/docs/sectionproperties/sp_csf_guide.md).
|
|
57
58
|
|
|
58
59
|
|
|
59
|
-
|
|
60
60
|
---
|
|
61
|
-
|
|
61
|
+
### Illustrative example
|
|
62
62
|
|
|
63
63
|
>Three-dimensional representation of the tapered pole with localized degradation. Gray regions indicate the non-degraded portions. Weight in this model is the elastic modulus.
|
|
64
64
|
|
|
@@ -83,6 +83,26 @@ It represents variable geometry and material distribution as continuous function
|
|
|
83
83
|
|
|
84
84
|
See [Ekofisk Jacket Platform - Foundation Piles](https://github.com/giovanniboscu/continuous-section-field/blob/main/actions-examples/ekofisk/README.md) for a localized corrosion case.
|
|
85
85
|
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
**CSF–CUF structural analysis**
|
|
89
|
+
|
|
90
|
+
CSF can be used as the continuous geometry and material provider for a
|
|
91
|
+
Carrera Unified Formulation (CUF) beam model.
|
|
92
|
+
|
|
93
|
+
The repository includes an open-source CUF implementation in which
|
|
94
|
+
cross-sectional expansion functions are independent from the CSF
|
|
95
|
+
geometry/material description.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd cuf/tutorials/variable_material_t_section/t_section/models/
|
|
99
|
+
csf-actions t_noprismatic_csf.yaml action.yaml # inspect the non-prismatic T-section
|
|
100
|
+
|
|
101
|
+
cd ../cases
|
|
102
|
+
csf-cuf bending_halfwave_legendre_N08.yaml # run a CUF bending analysis
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
➡️ [CSF–CUF framework and documentation](cuf/readme.md)
|
|
86
106
|
|
|
87
107
|
|
|
88
108
|
---
|
|
@@ -182,7 +202,7 @@ then run
|
|
|
182
202
|
**linux / Mac**
|
|
183
203
|
```
|
|
184
204
|
python3 -m venv venv
|
|
185
|
-
source
|
|
205
|
+
source venv/bin/activate
|
|
186
206
|
pip install csfpy
|
|
187
207
|
csf-actions geometry.yaml actions.yaml
|
|
188
208
|
```
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://doi.org/10.5281/zenodo.20687216)
|
|
8
8
|

|
|
9
|
+

|
|
9
10
|

|
|
10
11
|

|
|
11
12
|

|
|
@@ -28,9 +29,8 @@ It represents variable geometry and material distribution as continuous function
|
|
|
28
29
|
The resulting data can be exported in **CSV** and **YAML** formats for integration with beam solvers such as OpenSees and SAP2000, or with section-analysis tools such as [csf_sp](https://github.com/giovanniboscu/continuous-section-field/blob/main/docs/csf_sp_user_guide.md) and [sp_csf](https://github.com/giovanniboscu/continuous-section-field/blob/main/docs/sectionproperties/sp_csf_guide.md).
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
|
|
32
32
|
---
|
|
33
|
-
|
|
33
|
+
### Illustrative example
|
|
34
34
|
|
|
35
35
|
>Three-dimensional representation of the tapered pole with localized degradation. Gray regions indicate the non-degraded portions. Weight in this model is the elastic modulus.
|
|
36
36
|
|
|
@@ -55,6 +55,26 @@ It represents variable geometry and material distribution as continuous function
|
|
|
55
55
|
|
|
56
56
|
See [Ekofisk Jacket Platform - Foundation Piles](https://github.com/giovanniboscu/continuous-section-field/blob/main/actions-examples/ekofisk/README.md) for a localized corrosion case.
|
|
57
57
|
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
**CSF–CUF structural analysis**
|
|
61
|
+
|
|
62
|
+
CSF can be used as the continuous geometry and material provider for a
|
|
63
|
+
Carrera Unified Formulation (CUF) beam model.
|
|
64
|
+
|
|
65
|
+
The repository includes an open-source CUF implementation in which
|
|
66
|
+
cross-sectional expansion functions are independent from the CSF
|
|
67
|
+
geometry/material description.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cd cuf/tutorials/variable_material_t_section/t_section/models/
|
|
71
|
+
csf-actions t_noprismatic_csf.yaml action.yaml # inspect the non-prismatic T-section
|
|
72
|
+
|
|
73
|
+
cd ../cases
|
|
74
|
+
csf-cuf bending_halfwave_legendre_N08.yaml # run a CUF bending analysis
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
➡️ [CSF–CUF framework and documentation](cuf/readme.md)
|
|
58
78
|
|
|
59
79
|
|
|
60
80
|
---
|
|
@@ -154,7 +174,7 @@ then run
|
|
|
154
174
|
**linux / Mac**
|
|
155
175
|
```
|
|
156
176
|
python3 -m venv venv
|
|
157
|
-
source
|
|
177
|
+
source venv/bin/activate
|
|
158
178
|
pip install csfpy
|
|
159
179
|
csf-actions geometry.yaml actions.yaml
|
|
160
180
|
```
|
|
@@ -8,7 +8,7 @@ Repository = "https://github.com/giovanniboscu/continuous-section-field"
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "csfpy"
|
|
11
|
-
version = "v0.2.
|
|
11
|
+
version = "v0.2.3"
|
|
12
12
|
description = "An Analytical Engine for Non-Prismatic Structural Members"
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
authors = [{ name = "Giovanni Boscu" }]
|
|
@@ -45,11 +45,24 @@ elsewhere only by the prescribed half-wave factor.
|
|
|
45
45
|
|
|
46
46
|
Constraints
|
|
47
47
|
-----------
|
|
48
|
-
|
|
49
|
-
all global-y and global-z CUF amplitudes are fixed at both beam ends, and the
|
|
50
|
-
remaining rigid global-x translation is removed with the FEM3D point anchor
|
|
48
|
+
Both beam-end sections are perfectly clamped.
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
At ``x = x_start`` and ``x = x_end`` the complete displacement field is zero:
|
|
51
|
+
|
|
52
|
+
u_x = 0
|
|
53
|
+
u_y = 0
|
|
54
|
+
u_z = 0
|
|
55
|
+
|
|
56
|
+
over the whole cross-section. No pointwise axial anchor is used.
|
|
57
|
+
|
|
58
|
+
In CUF this is enforced by setting every generalized transverse amplitude to
|
|
59
|
+
zero for all three displacement components at both longitudinal end nodes.
|
|
60
|
+
Because the physical field is reconstructed as
|
|
61
|
+
|
|
62
|
+
u_i(x,y,z) = sum_tau F_tau(y,z) * u_i,tau(x),
|
|
63
|
+
|
|
64
|
+
zeroing every ``u_i,tau`` at an end node makes the complete physical
|
|
65
|
+
displacement component ``u_i`` vanish everywhere on that end section.
|
|
53
66
|
|
|
54
67
|
YAML interface
|
|
55
68
|
--------------
|
|
@@ -551,25 +564,47 @@ class SurfaceHalfWaveLoadProblem:
|
|
|
551
564
|
basis: Any,
|
|
552
565
|
longitudinal_integrator: Any,
|
|
553
566
|
):
|
|
554
|
-
"""
|
|
567
|
+
"""Perfectly clamp both beam ends: ux = uy = uz = 0.
|
|
555
568
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
569
|
+
CUF does not generally associate one generalized degree of freedom with
|
|
570
|
+
one physical point of the cross-section. The physical displacement is
|
|
571
|
+
reconstructed from the transverse expansion
|
|
572
|
+
|
|
573
|
+
u_i(x, y, z) = sum_tau F_tau(y, z) * u_i,tau(x).
|
|
574
|
+
|
|
575
|
+
Therefore a full sectional clamp is imposed by setting *all* CUF
|
|
576
|
+
amplitudes ``u_i,tau`` to zero, for all three physical components, at
|
|
577
|
+
each longitudinal end node. This avoids the previous pointwise axial
|
|
578
|
+
anchor and gives a boundary condition that is natural for the CUF
|
|
579
|
+
representation.
|
|
559
580
|
"""
|
|
560
581
|
|
|
561
582
|
layout = assembled.dof_layout
|
|
562
|
-
|
|
583
|
+
n_tau = int(basis.size)
|
|
584
|
+
|
|
585
|
+
# We constrain:
|
|
586
|
+
# 3 displacement components (x, y, z)
|
|
587
|
+
# x 2 longitudinal end sections (S0 and S1)
|
|
588
|
+
# x n_tau transverse CUF amplitudes.
|
|
589
|
+
#
|
|
590
|
+
# Hence the total number of scalar constraint equations is 6*n_tau.
|
|
591
|
+
row_count = 6 * n_tau
|
|
563
592
|
matrix = np.zeros((row_count, layout.total_dofs), dtype=float)
|
|
564
593
|
rhs = np.zeros(row_count, dtype=float)
|
|
565
594
|
row = 0
|
|
566
595
|
|
|
567
|
-
#
|
|
568
|
-
#
|
|
569
|
-
#
|
|
596
|
+
# Perfect clamp at both longitudinal ends.
|
|
597
|
+
#
|
|
598
|
+
# Solver component numbering is:
|
|
599
|
+
# 0 -> global x displacement
|
|
600
|
+
# 1 -> global y displacement
|
|
601
|
+
# 2 -> global z displacement
|
|
602
|
+
#
|
|
603
|
+
# Setting every generalized amplitude to zero for a component makes the
|
|
604
|
+
# complete reconstructed CUF displacement vanish over the whole section.
|
|
570
605
|
for node in (0, mesh.number_of_nodes - 1):
|
|
571
|
-
for component in (1, 2):
|
|
572
|
-
for tau in range(1,
|
|
606
|
+
for component in (0, 1, 2):
|
|
607
|
+
for tau in range(1, n_tau + 1):
|
|
573
608
|
matrix[
|
|
574
609
|
row,
|
|
575
610
|
layout.index(
|
|
@@ -580,39 +615,6 @@ class SurfaceHalfWaveLoadProblem:
|
|
|
580
615
|
] = 1.0
|
|
581
616
|
row += 1
|
|
582
617
|
|
|
583
|
-
# The end supports leave one rigid global-x translation. Match the
|
|
584
|
-
# FEM3D reference exactly by anchoring the physical displacement at the
|
|
585
|
-
# start section and at the section point (y, z) = (0, 0):
|
|
586
|
-
#
|
|
587
|
-
# u_x(x=0, y=0, z=0) = 0.
|
|
588
|
-
#
|
|
589
|
-
# At the first longitudinal FE node, the physical CUF displacement is
|
|
590
|
-
# the section-basis expansion sum_tau F_tau(0, 0) * q_x,tau.
|
|
591
|
-
axial_anchor_factors = np.asarray(
|
|
592
|
-
[
|
|
593
|
-
basis.value(
|
|
594
|
-
tau,
|
|
595
|
-
0.0,
|
|
596
|
-
0.0,
|
|
597
|
-
x=float(mesh.x_start),
|
|
598
|
-
)
|
|
599
|
-
for tau in range(1, int(basis.size) + 1)
|
|
600
|
-
],
|
|
601
|
-
dtype=float,
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
start_node = 0
|
|
605
|
-
for tau, factor in enumerate(axial_anchor_factors, start=1):
|
|
606
|
-
matrix[
|
|
607
|
-
row,
|
|
608
|
-
layout.index(
|
|
609
|
-
node=start_node,
|
|
610
|
-
tau=tau,
|
|
611
|
-
component=0,
|
|
612
|
-
),
|
|
613
|
-
] = float(factor)
|
|
614
|
-
|
|
615
|
-
row += 1
|
|
616
618
|
if row != row_count:
|
|
617
619
|
raise RuntimeError("internal constraint-row count mismatch")
|
|
618
620
|
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
# Version: CSF-CUF torsional line-pair half-wave adapter v1.
|
|
1
|
+
# Version: CSF-CUF torsional line-pair half-wave adapter v1.2-fixed-fixed - 2026-09-10
|
|
2
2
|
"""
|
|
3
3
|
Sinusoidal torsional line-pair problem adapter for CSF-CUF.
|
|
4
4
|
|
|
5
5
|
This adapter is the m=1 half-wave counterpart of ``torsion_uniform.py``. It
|
|
6
|
-
preserves
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
preserves the same opposite global-z force pair, the same longitudinal measure
|
|
7
|
+
``dx``, the same CUF projection, and the same FEM3D-compatible constraints.
|
|
8
|
+
The only mechanical change relative to the uniform adapter is the longitudinal
|
|
9
|
+
half-wave intensity.
|
|
10
10
|
|
|
11
11
|
Physical loading
|
|
12
12
|
----------------
|
|
13
|
-
|
|
13
|
+
The two load trajectories are defined once from the CSF end sections.
|
|
14
|
+
At ``x = x0`` and ``x = x1``:
|
|
14
15
|
|
|
15
16
|
* ``point_plus`` is the leftmost CSF vertex on the maximum-z boundary;
|
|
16
17
|
* ``point_minus`` is the rightmost CSF vertex on the minimum-z boundary.
|
|
17
18
|
|
|
19
|
+
The corresponding S0/S1 points define two straight physical load lines.
|
|
20
|
+
For every interior x, including longitudinal Gauss points, ``point_plus(x)``
|
|
21
|
+
and ``point_minus(x)`` are obtained by affine interpolation along those lines.
|
|
22
|
+
No repeated minimum/maximum search is performed on intermediate CSF sections.
|
|
23
|
+
|
|
18
24
|
The signed line-load intensities are
|
|
19
25
|
|
|
20
26
|
+ amplitude * sin(pi * (x - x0) / L)
|
|
@@ -41,12 +47,17 @@ introduced. Adding such a factor would define a different physical problem.
|
|
|
41
47
|
|
|
42
48
|
Constraints
|
|
43
49
|
-----------
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
Both beam ends are perfectly clamped.
|
|
51
|
+
|
|
52
|
+
At ``x = x_start`` and ``x = x_end`` the complete displacement field is zero:
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
u_x = 0
|
|
55
|
+
u_y = 0
|
|
56
|
+
u_z = 0
|
|
57
|
+
|
|
58
|
+
over the whole cross-section. No pointwise axial anchor is used. This is a
|
|
59
|
+
fixed-fixed (clamped-clamped) model and therefore suppresses axial warping at
|
|
60
|
+
both end sections.
|
|
50
61
|
|
|
51
62
|
YAML interface
|
|
52
63
|
--------------
|
|
@@ -120,11 +131,14 @@ def _has_vertex(section_provider: Any, x: float, point) -> bool:
|
|
|
120
131
|
|
|
121
132
|
|
|
122
133
|
def moving_load_points(section_provider: Any, x: float):
|
|
123
|
-
"""
|
|
134
|
+
"""Identify the two torsional load vertices on one CSF section.
|
|
124
135
|
|
|
125
136
|
The positive point is the leftmost vertex on the maximum-z boundary. The
|
|
126
137
|
negative point is the rightmost vertex on the minimum-z boundary. Ties are
|
|
127
138
|
resolved deterministically by the physical y coordinate.
|
|
139
|
+
|
|
140
|
+
For the half-wave projector this selector is used only at x0 and x1; the
|
|
141
|
+
interior load points then follow the straight lines joining those endpoints.
|
|
128
142
|
"""
|
|
129
143
|
|
|
130
144
|
x = float(x)
|
|
@@ -203,8 +217,36 @@ class HalfWaveTorsionalLinePairProjector:
|
|
|
203
217
|
|
|
204
218
|
self.length = self.x1 - self.x0
|
|
205
219
|
self.alpha = math.pi / self.length
|
|
220
|
+
|
|
221
|
+
# Identify the two physical load lines once from their CSF endpoints.
|
|
222
|
+
self.point_plus_0, self.point_minus_0 = moving_load_points(
|
|
223
|
+
self.section_provider,
|
|
224
|
+
self.x0,
|
|
225
|
+
)
|
|
226
|
+
self.point_plus_1, self.point_minus_1 = moving_load_points(
|
|
227
|
+
self.section_provider,
|
|
228
|
+
self.x1,
|
|
229
|
+
)
|
|
230
|
+
|
|
206
231
|
self._cache: dict[float, np.ndarray] = {}
|
|
207
232
|
|
|
233
|
+
def load_points(self, x: float):
|
|
234
|
+
"""Return the two points on the straight physical load trajectories."""
|
|
235
|
+
|
|
236
|
+
x = float(x)
|
|
237
|
+
t = (x - self.x0) / self.length
|
|
238
|
+
|
|
239
|
+
point_plus = (
|
|
240
|
+
(1.0 - t) * self.point_plus_0[0] + t * self.point_plus_1[0],
|
|
241
|
+
(1.0 - t) * self.point_plus_0[1] + t * self.point_plus_1[1],
|
|
242
|
+
)
|
|
243
|
+
point_minus = (
|
|
244
|
+
(1.0 - t) * self.point_minus_0[0] + t * self.point_minus_1[0],
|
|
245
|
+
(1.0 - t) * self.point_minus_0[1] + t * self.point_minus_1[1],
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
return point_plus, point_minus
|
|
249
|
+
|
|
208
250
|
def generalized_vector(self, x: float) -> np.ndarray:
|
|
209
251
|
"""Return the complete half-wave ``q_tau,z(x)`` vector."""
|
|
210
252
|
|
|
@@ -213,10 +255,7 @@ class HalfWaveTorsionalLinePairProjector:
|
|
|
213
255
|
if cached is not None:
|
|
214
256
|
return cached
|
|
215
257
|
|
|
216
|
-
point_plus, point_minus =
|
|
217
|
-
self.section_provider,
|
|
218
|
-
x,
|
|
219
|
-
)
|
|
258
|
+
point_plus, point_minus = self.load_points(x)
|
|
220
259
|
y_plus, z_plus = point_plus
|
|
221
260
|
y_minus, z_minus = point_minus
|
|
222
261
|
|
|
@@ -310,19 +349,29 @@ class TorsionHalfWaveProblem:
|
|
|
310
349
|
basis: Any,
|
|
311
350
|
longitudinal_integrator: Any,
|
|
312
351
|
):
|
|
313
|
-
"""
|
|
352
|
+
"""Perfectly clamp both beam ends: ux = uy = uz = 0."""
|
|
314
353
|
|
|
315
354
|
layout = assembled.dof_layout
|
|
316
|
-
|
|
355
|
+
n_tau = int(basis.size)
|
|
356
|
+
|
|
357
|
+
# Three displacement components x two beam ends x all transverse
|
|
358
|
+
# amplitudes.
|
|
359
|
+
row_count = 6 * n_tau
|
|
317
360
|
matrix = np.zeros((row_count, layout.total_dofs), dtype=float)
|
|
318
361
|
rhs = np.zeros(row_count, dtype=float)
|
|
319
362
|
row = 0
|
|
320
363
|
|
|
321
|
-
#
|
|
322
|
-
#
|
|
364
|
+
# Perfect clamp at both longitudinal ends.
|
|
365
|
+
#
|
|
366
|
+
# Fixing every generalized amplitude for components x, y and z makes
|
|
367
|
+
# the complete CUF displacement field vanish on each end section:
|
|
368
|
+
#
|
|
369
|
+
# u_x = u_y = u_z = 0
|
|
370
|
+
#
|
|
371
|
+
# for every physical point (y,z) of the section.
|
|
323
372
|
for node in (0, mesh.number_of_nodes - 1):
|
|
324
|
-
for component in (1, 2):
|
|
325
|
-
for tau in range(1,
|
|
373
|
+
for component in (0, 1, 2):
|
|
374
|
+
for tau in range(1, n_tau + 1):
|
|
326
375
|
matrix[
|
|
327
376
|
row,
|
|
328
377
|
layout.index(
|
|
@@ -333,33 +382,6 @@ class TorsionHalfWaveProblem:
|
|
|
333
382
|
] = 1.0
|
|
334
383
|
row += 1
|
|
335
384
|
|
|
336
|
-
# Remove only the rigid global-x translation, using exactly the same
|
|
337
|
-
# physical anchor as FEM3D: u_x(x_start, y=0, z=0) = 0.
|
|
338
|
-
axial_anchor_factors = np.asarray(
|
|
339
|
-
[
|
|
340
|
-
basis.value(
|
|
341
|
-
tau,
|
|
342
|
-
0.0,
|
|
343
|
-
0.0,
|
|
344
|
-
x=float(mesh.x_start),
|
|
345
|
-
)
|
|
346
|
-
for tau in range(1, int(basis.size) + 1)
|
|
347
|
-
],
|
|
348
|
-
dtype=float,
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
start_node = 0
|
|
352
|
-
for tau, factor in enumerate(axial_anchor_factors, start=1):
|
|
353
|
-
matrix[
|
|
354
|
-
row,
|
|
355
|
-
layout.index(
|
|
356
|
-
node=start_node,
|
|
357
|
-
tau=tau,
|
|
358
|
-
component=0,
|
|
359
|
-
),
|
|
360
|
-
] = float(factor)
|
|
361
|
-
|
|
362
|
-
row += 1
|
|
363
385
|
if row != row_count:
|
|
364
386
|
raise RuntimeError("internal constraint-row count mismatch")
|
|
365
387
|
|
|
@@ -413,4 +435,4 @@ __all__ = (
|
|
|
413
435
|
"HalfWaveTorsionalLinePairProjector",
|
|
414
436
|
"build_problem",
|
|
415
437
|
"moving_load_points",
|
|
416
|
-
)
|
|
438
|
+
)
|
|
@@ -20,7 +20,24 @@ LongitudinalTransverseDegree = Callable[[object], int]
|
|
|
20
20
|
|
|
21
21
|
@dataclass(frozen=True)
|
|
22
22
|
class CUFBasisPlugin:
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
"""Build a basis with access to the complete CSF field context.
|
|
25
|
+
|
|
26
|
+
Simple expansions may ignore ``continuous_section_field``.
|
|
27
|
+
|
|
28
|
+
The longitudinal coordinate ``x`` may be available during runtime basis
|
|
29
|
+
evaluation as contextual information for diagnostics, logging, tracing,
|
|
30
|
+
or bookkeeping.
|
|
31
|
+
|
|
32
|
+
In the current CSF-CUF formulation, however, transverse basis functions
|
|
33
|
+
remain mathematically independent of ``x``:
|
|
34
|
+
|
|
35
|
+
F_tau = F_tau(y, z)
|
|
36
|
+
|
|
37
|
+
Therefore ``x`` must not affect the returned basis value or its
|
|
38
|
+
transverse derivatives.
|
|
39
|
+
"""
|
|
40
|
+
|
|
24
41
|
|
|
25
42
|
name: str
|
|
26
43
|
builder: BasisBuilder
|
|
@@ -74,14 +74,7 @@ class ScaledLagrangeBasis(CUFBasis):
|
|
|
74
74
|
self._y_scale = y_scale
|
|
75
75
|
self._z_scale = z_scale
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
# solver-side displacement checkpoint consumes this optional generic
|
|
79
|
-
# representation without knowing which concrete expansion produced
|
|
80
|
-
# it. Future polynomial expansions may expose the same
|
|
81
|
-
# power_coefficients() method to opt into self-contained displacement
|
|
82
|
-
# checkpoints; the CUFBasis core contract remains unchanged.
|
|
83
|
-
self._power_coefficients = self._build_power_coefficients()
|
|
84
|
-
self._power_coefficients.setflags(write=False)
|
|
77
|
+
|
|
85
78
|
|
|
86
79
|
@property
|
|
87
80
|
def order(self) -> int:
|
|
@@ -106,124 +99,8 @@ class ScaledLagrangeBasis(CUFBasis):
|
|
|
106
99
|
|
|
107
100
|
return self._reference_basis.definition(tau)
|
|
108
101
|
|
|
109
|
-
|
|
110
|
-
"""Return F_tau coefficients in ascending physical powers of y and z.
|
|
111
|
-
|
|
112
|
-
The returned array has shape ``(size, order + 1, order + 1)`` and
|
|
113
|
-
follows
|
|
114
|
-
|
|
115
|
-
F_tau(y,z) = sum_{p,q} coefficients[tau-1,p,q] y**p z**q.
|
|
116
|
-
|
|
117
|
-
This optional expansion-owned export is used only to create a
|
|
118
|
-
self-contained displacement checkpoint. It is deliberately outside
|
|
119
|
-
the CUFBasis core interface so future non-polynomial expansions can
|
|
120
|
-
choose a different checkpoint representation without changing core.
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
return self._power_coefficients.copy()
|
|
124
|
-
|
|
125
|
-
@staticmethod
|
|
126
|
-
def _reference_polynomial(order: int, *, reverse: bool = False):
|
|
127
|
-
"""Return p_order(mu) or p_order(-mu) in ascending powers.
|
|
128
|
-
|
|
129
|
-
This representation is needed only by the optional physical-power
|
|
130
|
-
checkpoint export. The solver itself evaluates the reference
|
|
131
|
-
polynomial directly from its roots in ``core.basis``.
|
|
132
|
-
|
|
133
|
-
Coefficients are accumulated in extended precision before the final
|
|
134
|
-
float64 cast. The checkpoint must ultimately contain monomial
|
|
135
|
-
coefficients, but coefficient construction should not add avoidable
|
|
136
|
-
float64 roundoff.
|
|
137
|
-
"""
|
|
138
|
-
|
|
139
|
-
roots = np.linspace(-1.0, 1.0, int(order), dtype=np.longdouble)
|
|
140
|
-
coefficients = np.asarray((1.0,), dtype=np.longdouble)
|
|
141
|
-
|
|
142
|
-
# Ascending powers: multiply successively by (mu - root).
|
|
143
|
-
for root in roots:
|
|
144
|
-
coefficients = np.convolve(
|
|
145
|
-
coefficients,
|
|
146
|
-
np.asarray((-root, 1.0), dtype=np.longdouble),
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
if reverse:
|
|
150
|
-
coefficients = coefficients * np.power(
|
|
151
|
-
np.longdouble(-1.0),
|
|
152
|
-
np.arange(coefficients.size),
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
return np.asarray(coefficients, dtype=float)
|
|
156
|
-
|
|
157
|
-
def _build_power_coefficients(self) -> np.ndarray:
|
|
158
|
-
"""Compile every hierarchy term into physical y,z power coefficients."""
|
|
159
|
-
|
|
160
|
-
count = self.order + 1
|
|
161
|
-
coefficients = np.zeros((self.size, count, count), dtype=float)
|
|
162
|
-
|
|
163
|
-
for tau in range(1, self.size + 1):
|
|
164
|
-
kind, r, side, n, m = self.definition(tau)
|
|
165
|
-
|
|
166
|
-
if kind == "I":
|
|
167
|
-
corner_signs = (
|
|
168
|
-
(-1.0, -1.0),
|
|
169
|
-
(+1.0, -1.0),
|
|
170
|
-
(+1.0, +1.0),
|
|
171
|
-
(-1.0, +1.0),
|
|
172
|
-
)
|
|
173
|
-
sign_y, sign_z = corner_signs[side - 1]
|
|
174
|
-
reference = 0.25 * np.outer(
|
|
175
|
-
np.asarray((1.0, sign_y)),
|
|
176
|
-
np.asarray((1.0, sign_z)),
|
|
177
|
-
)
|
|
178
|
-
elif kind in ("IIA", "IIB"):
|
|
179
|
-
if side == 1:
|
|
180
|
-
reference = 0.5 * np.outer(
|
|
181
|
-
self._reference_polynomial(r),
|
|
182
|
-
np.asarray((1.0, -1.0)),
|
|
183
|
-
)
|
|
184
|
-
elif side == 2:
|
|
185
|
-
reference = 0.5 * np.outer(
|
|
186
|
-
np.asarray((1.0, +1.0)),
|
|
187
|
-
self._reference_polynomial(r),
|
|
188
|
-
)
|
|
189
|
-
elif side == 3:
|
|
190
|
-
reference = 0.5 * np.outer(
|
|
191
|
-
self._reference_polynomial(r, reverse=True),
|
|
192
|
-
np.asarray((1.0, +1.0)),
|
|
193
|
-
)
|
|
194
|
-
elif side == 4:
|
|
195
|
-
reference = 0.5 * np.outer(
|
|
196
|
-
np.asarray((1.0, -1.0)),
|
|
197
|
-
self._reference_polynomial(r, reverse=True),
|
|
198
|
-
)
|
|
199
|
-
else:
|
|
200
|
-
raise RuntimeError("invalid SL edge index")
|
|
201
|
-
elif kind == "III":
|
|
202
|
-
reference = np.outer(
|
|
203
|
-
self._reference_polynomial(n),
|
|
204
|
-
self._reference_polynomial(m),
|
|
205
|
-
)
|
|
206
|
-
else:
|
|
207
|
-
raise RuntimeError(
|
|
208
|
-
f"unsupported SL function type {kind!r}"
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
rows, columns = reference.shape
|
|
212
|
-
y_scaling = np.power(
|
|
213
|
-
self._y_scale,
|
|
214
|
-
-np.arange(rows, dtype=float),
|
|
215
|
-
)
|
|
216
|
-
z_scaling = np.power(
|
|
217
|
-
self._z_scale,
|
|
218
|
-
-np.arange(columns, dtype=float),
|
|
219
|
-
)
|
|
220
|
-
coefficients[tau - 1, :rows, :columns] = (
|
|
221
|
-
reference
|
|
222
|
-
* y_scaling[:, None]
|
|
223
|
-
* z_scaling[None, :]
|
|
224
|
-
)
|
|
102
|
+
|
|
225
103
|
|
|
226
|
-
return coefficients
|
|
227
104
|
|
|
228
105
|
def value(
|
|
229
106
|
self,
|
|
@@ -71,6 +71,148 @@ class CompiledDisplacementField:
|
|
|
71
71
|
):
|
|
72
72
|
value.setflags(write=False)
|
|
73
73
|
|
|
74
|
+
##
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _transverse_power_coefficients(basis):
|
|
79
|
+
"""
|
|
80
|
+
Reconstruct a polynomial transverse basis from the normal CUF basis API.
|
|
81
|
+
|
|
82
|
+
No checkpoint-specific method is required from the expansion.
|
|
83
|
+
The reconstruction uses the existing basis order, transverse scales,
|
|
84
|
+
and basis evaluations.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
order = int(basis.order)
|
|
89
|
+
size = int(basis.size)
|
|
90
|
+
y_scale, z_scale = map(float, basis.scales)
|
|
91
|
+
except (AttributeError, TypeError, ValueError):
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
if order < 0 or size < 1:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
if (
|
|
98
|
+
not np.isfinite(y_scale)
|
|
99
|
+
or not np.isfinite(z_scale)
|
|
100
|
+
or y_scale <= 0.0
|
|
101
|
+
or z_scale <= 0.0
|
|
102
|
+
):
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
count = order + 1
|
|
106
|
+
|
|
107
|
+
# Chebyshev-Lobatto interpolation points in normalized coordinates.
|
|
108
|
+
if count == 1:
|
|
109
|
+
points = np.asarray((0.0,), dtype=float)
|
|
110
|
+
else:
|
|
111
|
+
points = np.polynomial.chebyshev.chebpts2(count)
|
|
112
|
+
|
|
113
|
+
# V[i,p] = T_p(points[i])
|
|
114
|
+
vandermonde = np.polynomial.chebyshev.chebvander(
|
|
115
|
+
points,
|
|
116
|
+
order,
|
|
117
|
+
)
|
|
118
|
+
inverse_vandermonde = np.linalg.inv(vandermonde)
|
|
119
|
+
|
|
120
|
+
# values[tau, iy, iz]
|
|
121
|
+
values = np.empty(
|
|
122
|
+
(size, count, count),
|
|
123
|
+
dtype=float,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
for iy, normalized_y in enumerate(points):
|
|
127
|
+
y = y_scale * float(normalized_y)
|
|
128
|
+
|
|
129
|
+
for iz, normalized_z in enumerate(points):
|
|
130
|
+
z = z_scale * float(normalized_z)
|
|
131
|
+
|
|
132
|
+
if hasattr(basis, "values"):
|
|
133
|
+
current = np.asarray(
|
|
134
|
+
basis.values(y, z),
|
|
135
|
+
dtype=float,
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
current = np.asarray(
|
|
139
|
+
[
|
|
140
|
+
basis.value(tau, y, z)
|
|
141
|
+
for tau in range(1, size + 1)
|
|
142
|
+
],
|
|
143
|
+
dtype=float,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if current.shape != (size,):
|
|
147
|
+
raise ValueError(
|
|
148
|
+
"transverse basis evaluation has invalid shape"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
values[:, iy, iz] = current
|
|
152
|
+
|
|
153
|
+
# Tensor-product interpolation:
|
|
154
|
+
#
|
|
155
|
+
# values = V * C_chebyshev * V^T
|
|
156
|
+
chebyshev_coefficients = np.einsum(
|
|
157
|
+
"ai,tij,bj->tab",
|
|
158
|
+
inverse_vandermonde,
|
|
159
|
+
values,
|
|
160
|
+
inverse_vandermonde,
|
|
161
|
+
optimize=True,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Transformation matrix:
|
|
165
|
+
#
|
|
166
|
+
# Chebyshev coefficients -> ordinary powers
|
|
167
|
+
conversion = np.zeros((count, count), dtype=float)
|
|
168
|
+
|
|
169
|
+
for degree in range(count):
|
|
170
|
+
unit = np.zeros(count, dtype=float)
|
|
171
|
+
unit[degree] = 1.0
|
|
172
|
+
|
|
173
|
+
polynomial = np.polynomial.chebyshev.cheb2poly(unit)
|
|
174
|
+
|
|
175
|
+
conversion[: polynomial.size, degree] = polynomial
|
|
176
|
+
|
|
177
|
+
normalized_power_coefficients = np.einsum(
|
|
178
|
+
"pa,tab,qb->tpq",
|
|
179
|
+
conversion,
|
|
180
|
+
chebyshev_coefficients,
|
|
181
|
+
conversion,
|
|
182
|
+
optimize=True,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Convert normalized powers:
|
|
186
|
+
#
|
|
187
|
+
# Y = y / y_scale
|
|
188
|
+
# Z = z / z_scale
|
|
189
|
+
#
|
|
190
|
+
# into physical powers y^p z^q.
|
|
191
|
+
y_scaling = np.power(
|
|
192
|
+
y_scale,
|
|
193
|
+
-np.arange(count, dtype=float),
|
|
194
|
+
)
|
|
195
|
+
z_scaling = np.power(
|
|
196
|
+
z_scale,
|
|
197
|
+
-np.arange(count, dtype=float),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
coefficients = (
|
|
201
|
+
normalized_power_coefficients
|
|
202
|
+
* y_scaling[None, :, None]
|
|
203
|
+
* z_scaling[None, None, :]
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if not np.all(np.isfinite(coefficients)):
|
|
207
|
+
raise ValueError(
|
|
208
|
+
"compiled transverse polynomial contains non-finite values"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return np.asarray(coefficients, dtype=float)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
74
216
|
@classmethod
|
|
75
217
|
def from_solution_data(
|
|
76
218
|
cls,
|
|
@@ -88,10 +230,7 @@ class CompiledDisplacementField:
|
|
|
88
230
|
expansions continue to use the existing in-memory recovery path and
|
|
89
231
|
are not altered by this feature.
|
|
90
232
|
"""
|
|
91
|
-
|
|
92
|
-
export = getattr(basis, "power_coefficients", None)
|
|
93
|
-
if not callable(export):
|
|
94
|
-
return None
|
|
233
|
+
|
|
95
234
|
|
|
96
235
|
solved_dofs = np.asarray(solved_dofs, dtype=float)
|
|
97
236
|
if solved_dofs.shape != (dof_layout.total_dofs,):
|
|
@@ -135,7 +274,11 @@ class CompiledDisplacementField:
|
|
|
135
274
|
longitudinal_coefficients = cls._lagrange_power_coefficients(
|
|
136
275
|
reference_nodes
|
|
137
276
|
)
|
|
138
|
-
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
transverse_coefficients = cls._transverse_power_coefficients(basis)
|
|
280
|
+
if transverse_coefficients is None:
|
|
281
|
+
return None
|
|
139
282
|
|
|
140
283
|
combined_metadata = dict(metadata or {})
|
|
141
284
|
combined_metadata.update(
|
|
@@ -415,14 +415,10 @@ def _longitudinal_gauss_requirement(
|
|
|
415
415
|
"must be (y,z) pairs"
|
|
416
416
|
)
|
|
417
417
|
|
|
418
|
-
if not close(vm, 0.5 * (v0 + v1)):
|
|
419
|
-
raise ValueError(
|
|
420
|
-
"cannot estimate longitudinal Gauss order: CSF geometry is "
|
|
421
|
-
f"not affine in x for domain {domain_index}"
|
|
422
|
-
)
|
|
423
418
|
|
|
424
|
-
varies_y =
|
|
425
|
-
varies_z =
|
|
419
|
+
varies_y = False
|
|
420
|
+
varies_z = False
|
|
421
|
+
|
|
426
422
|
|
|
427
423
|
configured_material_degree = (
|
|
428
424
|
case.longitudinal.material_polynomial_degree
|
|
@@ -477,7 +473,8 @@ def _longitudinal_gauss_requirement(
|
|
|
477
473
|
# here
|
|
478
474
|
# contribution of the interaction to the degree of the polynomial
|
|
479
475
|
#################################################################
|
|
480
|
-
varying_axes = int(varies_y) + int(varies_z)
|
|
476
|
+
#varying_axes = int(varies_y) + int(varies_z)
|
|
477
|
+
|
|
481
478
|
r = int(case.longitudinal.order)
|
|
482
479
|
transverse_x_degree = (
|
|
483
480
|
basis_plugin.transverse_x_polynomial_degree(basis)
|
|
@@ -565,21 +562,30 @@ def _longitudinal_gauss_requirement(
|
|
|
565
562
|
###minimum_gauss_order = (polynomial_degree + 2) // 2
|
|
566
563
|
######
|
|
567
564
|
|
|
568
|
-
axes = []
|
|
569
|
-
if varies_y:
|
|
570
|
-
|
|
571
|
-
if varies_z:
|
|
572
|
-
|
|
565
|
+
#axes = []
|
|
566
|
+
#if varies_y:
|
|
567
|
+
# axes.append("y")
|
|
568
|
+
#if varies_z:
|
|
569
|
+
# axes.append("z")
|
|
573
570
|
|
|
574
571
|
|
|
572
|
+
#return {
|
|
573
|
+
# "polynomial_degree": int(polynomial_degree),
|
|
574
|
+
# "minimum_gauss_order": int(minimum_gauss_order),
|
|
575
|
+
# "varying_axes": tuple(axes),
|
|
576
|
+
# "material_varies": bool(material_varies),
|
|
577
|
+
# "material_polynomial_degree": int(material_degree),
|
|
578
|
+
# "material_degree_source": material_degree_source,
|
|
579
|
+
#}
|
|
580
|
+
|
|
575
581
|
return {
|
|
576
582
|
"polynomial_degree": int(polynomial_degree),
|
|
577
583
|
"minimum_gauss_order": int(minimum_gauss_order),
|
|
578
|
-
"varying_axes": tuple(axes),
|
|
579
584
|
"material_varies": bool(material_varies),
|
|
580
585
|
"material_polynomial_degree": int(material_degree),
|
|
581
586
|
"material_degree_source": material_degree_source,
|
|
582
|
-
}
|
|
587
|
+
}
|
|
588
|
+
|
|
583
589
|
|
|
584
590
|
def solve_case(case, model_bridge, problem, *, progress: bool = True) -> CSFCUFSolution:
|
|
585
591
|
"""
|
|
@@ -673,17 +679,12 @@ def solve_case(case, model_bridge, problem, *, progress: bool = True) -> CSFCUFS
|
|
|
673
679
|
)
|
|
674
680
|
|
|
675
681
|
if progress:
|
|
676
|
-
|
|
677
|
-
axes_text = ",".join(axes) if axes else "none"
|
|
682
|
+
|
|
678
683
|
print(
|
|
679
684
|
f"[quadrature] longitudinal degree estimate = "
|
|
680
685
|
f"{longitudinal_requirement['polynomial_degree']}",
|
|
681
686
|
flush=True,
|
|
682
687
|
)
|
|
683
|
-
print(
|
|
684
|
-
f"[quadrature] longitudinal variation axes = {axes_text}",
|
|
685
|
-
flush=True,
|
|
686
|
-
)
|
|
687
688
|
print(
|
|
688
689
|
f"[quadrature] longitudinal material varies = "
|
|
689
690
|
f"{str(longitudinal_requirement['material_varies']).lower()}",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csfpy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: An Analytical Engine for Non-Prismatic Structural Members
|
|
5
5
|
Author: Giovanni Boscu
|
|
6
6
|
License: GPL-3.0
|
|
@@ -34,6 +34,7 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|
[](https://doi.org/10.5281/zenodo.20687216)
|
|
36
36
|

|
|
37
|
+

|
|
37
38
|

|
|
38
39
|

|
|
39
40
|

|
|
@@ -56,9 +57,8 @@ It represents variable geometry and material distribution as continuous function
|
|
|
56
57
|
The resulting data can be exported in **CSV** and **YAML** formats for integration with beam solvers such as OpenSees and SAP2000, or with section-analysis tools such as [csf_sp](https://github.com/giovanniboscu/continuous-section-field/blob/main/docs/csf_sp_user_guide.md) and [sp_csf](https://github.com/giovanniboscu/continuous-section-field/blob/main/docs/sectionproperties/sp_csf_guide.md).
|
|
57
58
|
|
|
58
59
|
|
|
59
|
-
|
|
60
60
|
---
|
|
61
|
-
|
|
61
|
+
### Illustrative example
|
|
62
62
|
|
|
63
63
|
>Three-dimensional representation of the tapered pole with localized degradation. Gray regions indicate the non-degraded portions. Weight in this model is the elastic modulus.
|
|
64
64
|
|
|
@@ -83,6 +83,26 @@ It represents variable geometry and material distribution as continuous function
|
|
|
83
83
|
|
|
84
84
|
See [Ekofisk Jacket Platform - Foundation Piles](https://github.com/giovanniboscu/continuous-section-field/blob/main/actions-examples/ekofisk/README.md) for a localized corrosion case.
|
|
85
85
|
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
**CSF–CUF structural analysis**
|
|
89
|
+
|
|
90
|
+
CSF can be used as the continuous geometry and material provider for a
|
|
91
|
+
Carrera Unified Formulation (CUF) beam model.
|
|
92
|
+
|
|
93
|
+
The repository includes an open-source CUF implementation in which
|
|
94
|
+
cross-sectional expansion functions are independent from the CSF
|
|
95
|
+
geometry/material description.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd cuf/tutorials/variable_material_t_section/t_section/models/
|
|
99
|
+
csf-actions t_noprismatic_csf.yaml action.yaml # inspect the non-prismatic T-section
|
|
100
|
+
|
|
101
|
+
cd ../cases
|
|
102
|
+
csf-cuf bending_halfwave_legendre_N08.yaml # run a CUF bending analysis
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
➡️ [CSF–CUF framework and documentation](cuf/readme.md)
|
|
86
106
|
|
|
87
107
|
|
|
88
108
|
---
|
|
@@ -182,7 +202,7 @@ then run
|
|
|
182
202
|
**linux / Mac**
|
|
183
203
|
```
|
|
184
204
|
python3 -m venv venv
|
|
185
|
-
source
|
|
205
|
+
source venv/bin/activate
|
|
186
206
|
pip install csfpy
|
|
187
207
|
csf-actions geometry.yaml actions.yaml
|
|
188
208
|
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|