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.
Files changed (88) hide show
  1. {csfpy-0.2.1/src/csfpy.egg-info → csfpy-0.2.3}/PKG-INFO +24 -4
  2. {csfpy-0.2.1 → csfpy-0.2.3}/README.md +23 -3
  3. {csfpy-0.2.1 → csfpy-0.2.3}/pyproject.toml +1 -1
  4. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/surface_halfwave.py +49 -47
  5. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/torsion_halfwave.py +72 -50
  6. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/basis_plugins.py +18 -1
  7. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_lagrange.py +2 -125
  8. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/compiled_field.py +148 -5
  9. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/engine.py +22 -21
  10. {csfpy-0.2.1 → csfpy-0.2.3/src/csfpy.egg-info}/PKG-INFO +24 -4
  11. {csfpy-0.2.1 → csfpy-0.2.3}/LICENSE +0 -0
  12. {csfpy-0.2.1 → csfpy-0.2.3}/setup.cfg +0 -0
  13. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/CSFActions.py +0 -0
  14. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/CSFStacked.py +0 -0
  15. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/__init__.py +0 -0
  16. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/_tol.py +0 -0
  17. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/__init__.py +0 -0
  18. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/export_yaml.py +0 -0
  19. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_properties.py +0 -0
  20. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_section_2d.py +0 -0
  21. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_shear_weight.py +0 -0
  22. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_volume_3d.py +0 -0
  23. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/plot_weight.py +0 -0
  24. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/section_area_by_weight.py +0 -0
  25. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/section_selected_analysis.py +0 -0
  26. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/volume.py +0 -0
  27. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/weight_lab_zrelative.py +0 -0
  28. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/write_opensees_geometry.py +0 -0
  29. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/actions/write_sap2000_geometry.py +0 -0
  30. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/continuous_section_field.py +0 -0
  31. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/__init__.py +0 -0
  32. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/__main__.py +0 -0
  33. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/output/post.py +0 -0
  34. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/bending_torsion_halfwave.py +0 -0
  35. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/torsion_uniform.py +0 -0
  36. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/adapters/problem/uniform_surface.py +0 -0
  37. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/case.py +0 -0
  38. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/__init__.py +0 -0
  39. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/basis.py +0 -0
  40. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/closed_form_sectional.py +0 -0
  41. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/integration.py +0 -0
  42. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/material.py +0 -0
  43. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/nucleus.py +0 -0
  44. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/section.py +0 -0
  45. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/sectional.py +0 -0
  46. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/core/sectional_geometry.py +0 -0
  47. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/csf_bridge.py +0 -0
  48. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/__init__.py +0 -0
  49. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_lagrange_q1.py +0 -0
  50. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_legendre.py +0 -0
  51. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_maclaurin.py +0 -0
  52. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/expansions/scaled_maclaurin_tensor.py +0 -0
  53. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/maclaurin_tensor.py +0 -0
  54. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/numerics.py +0 -0
  55. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/problem/__init__.py +0 -0
  56. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/problem/point_bc.py +0 -0
  57. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/problem/problem.py +0 -0
  58. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/problem/problem_api.py +0 -0
  59. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/__init__.py +0 -0
  60. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/assembly.py +0 -0
  61. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/augmented_solver.py +0 -0
  62. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/element.py +0 -0
  63. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/linear_constraint.py +0 -0
  64. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/longitudinal.py +0 -0
  65. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/solver/recovery.py +0 -0
  66. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/cuf/tools/evaluate_compiled_displacement.py +0 -0
  67. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/entities.py +0 -0
  68. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/io/__init__.py +0 -0
  69. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/io/csf_issues.py +0 -0
  70. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/io/csf_reader.py +0 -0
  71. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/io/csf_rough_validator.py +0 -0
  72. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/polygon_stress.py +0 -0
  73. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/section_field.py +0 -0
  74. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/__init__.py +0 -0
  75. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/csf_create_cone.py +0 -0
  76. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/csf_polygon_hole_builder_v2.py +0 -0
  77. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/csf_sp.py +0 -0
  78. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/csf_torsion_fem.py +0 -0
  79. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/sp_csf.py +0 -0
  80. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/writegeometry_rio_v2.py +0 -0
  81. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/utils/writegeometry_tappered_pole.py +0 -0
  82. {csfpy-0.2.1 → csfpy-0.2.3}/src/csf/visualizer.py +0 -0
  83. {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/SOURCES.txt +0 -0
  84. {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/dependency_links.txt +0 -0
  85. {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/entry_points.txt +0 -0
  86. {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/requires.txt +0 -0
  87. {csfpy-0.2.1 → csfpy-0.2.3}/src/csfpy.egg-info/top_level.txt +0 -0
  88. {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.1
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
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20687216.svg)](https://doi.org/10.5281/zenodo.20687216)
36
36
  ![Python](https://img.shields.io/badge/python-3.8+-blue.svg)
37
+ ![CUF](https://img.shields.io/badge/CUF-Carrera_Unified_Formulation-purple.svg)
37
38
  ![Math](https://img.shields.io/badge/Engine-Analytical_Mechanics-red.svg)
38
39
  ![License](https://img.shields.io/github/license/giovanniboscu/continuous-section-field)
39
40
  ![Repo Size](https://img.shields.io/github/repo-size/giovanniboscu/continuous-section-field)
@@ -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 source venv/bin/activate
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
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20687216.svg)](https://doi.org/10.5281/zenodo.20687216)
8
8
  ![Python](https://img.shields.io/badge/python-3.8+-blue.svg)
9
+ ![CUF](https://img.shields.io/badge/CUF-Carrera_Unified_Formulation-purple.svg)
9
10
  ![Math](https://img.shields.io/badge/Engine-Analytical_Mechanics-red.svg)
10
11
  ![License](https://img.shields.io/github/license/giovanniboscu/continuous-section-field)
11
12
  ![Repo Size](https://img.shields.io/github/repo-size/giovanniboscu/continuous-section-field)
@@ -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 source venv/bin/activate
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.1"
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
- The constraints are copied mechanically from ``uniform_surface_load.py``:
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
- u_x(x_start, 0, 0) = 0.
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
- """Apply the bending supports and the FEM3D pointwise axial anchor.
567
+ """Perfectly clamp both beam ends: ux = uy = uz = 0.
555
568
 
556
- Global y and z amplitudes are fixed at both longitudinal ends. The
557
- remaining rigid global-x translation is removed with the same anchor
558
- used by the reference FEM3D model: ``u_x(0, 0, 0) = 0``.
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
- row_count = 4 * int(basis.size) + 1
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
- # Fix every transverse CUF amplitude (global y and z components) at
568
- # both longitudinal ends. Component numbering follows the solver's
569
- # established convention: 0=x, 1=y, 2=z.
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, int(basis.size) + 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.0 - 2026-09-04
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 exactly the same CSF moving vertices, the same opposite global-z
7
- force pair, the same longitudinal measure ``dx``, the same CUF projection, and
8
- the same FEM3D-compatible constraints. The only mechanical change relative to
9
- the uniform adapter is the longitudinal half-wave intensity.
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
- At every physical coordinate x:
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
- The constraints are copied mechanically from the current uniform torsion CUF
45
- adapter and match FEM3D:
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
- * all global-y and global-z CUF amplitudes are fixed at both beam ends;
48
- * the remaining rigid global-x translation is removed with
49
- ``u_x(x_start, 0, 0) = 0``.
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
- """Return the same two moving CSF vertices as the half-wave torsion case.
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 = moving_load_points(
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
- """Apply the torsion supports with the FEM3D pointwise axial anchor."""
352
+ """Perfectly clamp both beam ends: ux = uy = uz = 0."""
314
353
 
315
354
  layout = assembled.dof_layout
316
- row_count = 4 * int(basis.size) + 1
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
- # Fix all generalized global-y and global-z amplitudes at both beam
322
- # ends. Solver component numbering is 0=x, 1=y, 2=z.
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, int(basis.size) + 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
- """Runtime descriptor for one transverse CUF basis implementation."""
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
- # Build the physical-coordinate power representation once. The
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
- def power_coefficients(self) -> np.ndarray:
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
- transverse_coefficients = np.asarray(export(), dtype=float)
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 = varies_y or (not close(v0[:, 0], v1[:, 0]))
425
- varies_z = varies_z or (not close(v0[:, 1], v1[:, 1]))
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
- axes.append("y")
571
- if varies_z:
572
- axes.append("z")
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
- axes = longitudinal_requirement["varying_axes"]
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.1
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
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20687216.svg)](https://doi.org/10.5281/zenodo.20687216)
36
36
  ![Python](https://img.shields.io/badge/python-3.8+-blue.svg)
37
+ ![CUF](https://img.shields.io/badge/CUF-Carrera_Unified_Formulation-purple.svg)
37
38
  ![Math](https://img.shields.io/badge/Engine-Analytical_Mechanics-red.svg)
38
39
  ![License](https://img.shields.io/github/license/giovanniboscu/continuous-section-field)
39
40
  ![Repo Size](https://img.shields.io/github/repo-size/giovanniboscu/continuous-section-field)
@@ -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 source venv/bin/activate
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