Skip to content

Update for Capytaine version 3 - #454

Open
mancellin wants to merge 10 commits into
sandialabs:devfrom
mancellin:update_for_capytaine_3
Open

Update for Capytaine version 3#454
mancellin wants to merge 10 commits into
sandialabs:devfrom
mancellin:update_for_capytaine_3

Conversation

@mancellin

Copy link
Copy Markdown

To be compatible with the upcoming version of Capytaine (and stay compatible with 2.3.1), you should replace load_from_meshio by the generic load_mesh.

Also, setting the rotation center of a body independently from the dofs does not really make sense in Capytaine, so I propose to remove it.

@jtgrasb

jtgrasb commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

@mancellin Thanks for these updates. I made a small change removing the function initialization for set_fb_centers to try to get tests to pass.

Looks like the tests are now failing due to the removal of set_fb_centers. I believe we added that function so that it would add a default rotation_center equal to the center_of_mass when one isn't provided, allowing the hydrostatics to be calculated. Now, without that function, its giving an error that the rotation center is not able to be inferred. What would you suggest doing instead to handle the rotation center?

@mancellin

Copy link
Copy Markdown
Author

My bad, the removal of the set_fb_centers works with Capytaine v3, but I had not run the test with 2.3.1. This is a bug of version 2.x for body with only translation dofs: the hydrostatics looks for a rotation center even if it does not need any.

Overall, the philosophy behind almost all the breaking changes in the recent version of Capytaine is that everything about (for instance) a body should be set at initialization and nothing should be updated later as set_fb_centers does.

On second thought, even the code I left from set_fb_centers:

if getattr(wec_im, "center_of_mass", None) is None:
        wec_im.center_of_mass = wec_im.center_of_buoyancy

will fail for the new Multibody class that does not have a single center_of_mass or center_of_buoyancy.

@coveralls

coveralls commented Aug 26, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 87.977% (+0.01%) from 87.964% — mancellin:update_for_capytaine_3 into sandialabs:dev

@jtgrasb

jtgrasb commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

@mancellin Thanks for the updates.

Since it sounds like the code you left will fail in v3 but is necessary in v2.x, should we wait until v3 is released and bug free, then switch over to using that format? For now, we could just require Capytaine <=2.3.1 until we're ready to switch.

@mancellin

Copy link
Copy Markdown
Author

v3 has been published last week and the failing CI job (build documentation) is using it. It fails because I forgot to remove a direct setting of rotation center which has been disabled in v3.

@mancellin

Copy link
Copy Markdown
Author

PS: feel free to add a temporary "capytaine<3" bound in your pyproject.toml if this is blocking some workflow somewhere.

@jtgrasb

jtgrasb commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

@mancellin Ahh okay, for some reason I thought I saw that v3 hadn't been released yet.

I'm testing out the cases locally and ran into some other errors as well.

  • In the LUPA tutorial, we use spar_fb.mesh.axis_aligned_bbox but this is no longer available. Is there a replacement function that can be used?
  • In the LUPA tutorial, we define heave dofs for the two bodies, then define the surge and pitch for both bodies together. However, it seems with v3, the two bodies together are defined as a Multibody object rather than a floating body and dofs can't be added. Do you have a suggestion for how to handle this?

Also, could you adjust this PR so it goes into the dev branch instead of main?

@mancellin
mancellin changed the base branch from main to dev August 28, 2026 08:29
@mancellin

Copy link
Copy Markdown
Author

In the LUPA tutorial, we use spar_fb.mesh.axis_aligned_bbox but this is no longer available. Is there a replacement function that can be used?

No, but you can reimplement your own easily:

def axis_aligned_bbox(mesh):
    x, y, z = mesh.vertices.T
    return (x.min(), x.max(), y.min(), y.max(), z.min(), z.max())

In the LUPA tutorial, we define heave dofs for the two bodies, then define the surge and pitch for both bodies together. However, it seems with v3, the two bodies together are defined as a Multibody object rather than a floating body and dofs can't be added. Do you have a suggestion for how to handle this?

Adding "global" dofs to a Multibody is not supported yet. As a workaround you should be able to reproduce the behavior of the previous version by merging the multibody into a single one and adding dofs to it.

both = (body_1 + body_2).as_FloatingBody
both.add_rigid_body_dofs(...)

@jtgrasb

jtgrasb commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Thank you. I am working through the updates in the tutorials. I used the as_FloatingBody method for LUPA and that seemed to work well. However, I am not sure how to add the combined-body dofs correctly. I don't see an .add_rigid_body_dofs(...) function and adding them individually works initially but fails when computing the hydrostatic stiffness.

# floating body
lupa_fb = (float_fb + spar_fb).as_FloatingBody

# center of mass LUPA
cm_lupa = ((mass_float*cm_float + mass_spar*cm_spar)
                        / (mass_float + mass_spar))

# additional DOFs
lupa_fb.add_translation_dof(name='Surge')
lupa_fb.add_rotation_dof(name='Pitch', rotation_center = cm_lupa)

# Calculate hydrostatic stiffness after keeping immersed value
lupa_fb = lupa_fb.immersed_part()
lupa_fb.hydrostatic_stiffness = lupa_fb.compute_hydrostatic_stiffness(rho=rho)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[12], [line 3](vscode-notebook-cell:?execution_count=12&line=3)
      1 # Calculate hydrostatic stiffness after keeping immersed value
      2 lupa_fb = lupa_fb.immersed_part()
----> [3](vscode-notebook-cell:?execution_count=12&line=3) lupa_fb.hydrostatic_stiffness = lupa_fb.compute_hydrostatic_stiffness(rho=rho)

File c:\Users\jtgrasb\AppData\Local\anaconda3\envs\wot_cpy3\Lib\site-packages\capytaine\bodies\hydrostatics.py:326, in _FloatingBodyHydrostaticsMixin.compute_hydrostatic_stiffness(self, divergence, rho, g)
    322         LOG.warning("Computing hydrostatic stiffness without the divergence of {}".format(influenced_dof))
    323         return 0.0
    325 hs_set =  xr.merge([
--> [326](file:///C:/Users/jtgrasb/AppData/Local/anaconda3/envs/wot_cpy3/Lib/site-packages/capytaine/bodies/hydrostatics.py:326)     self.each_hydrostatic_stiffness(
    327         influenced_dof_name, radiating_dof_name,
    328         influenced_dof_div = divergence_dof(influenced_dof_name),
    329         rho=rho, g=g
    330         )
    331     for radiating_dof_name in self.dofs
    332     for influenced_dof_name in self.dofs
    333     ], compat='no_conflicts', join="outer")
    335 # Reorder dofs
    336 K = hs_set.hydrostatic_stiffness.sel(influenced_dof=list(self.dofs.keys()), radiating_dof=list(self.dofs.keys()))

File c:\Users\jtgrasb\AppData\Local\anaconda3\envs\wot_cpy3\Lib\site-packages\capytaine\bodies\hydrostatics.py:248, in _FloatingBodyHydrostaticsMixin.each_hydrostatic_stiffness(self, influenced_dof_name, radiating_dof_name, influenced_dof_div, rho, g)
    245 else:
    246     raise ValueError(f"Incompatible shape of influenced_dof_div: {influenced_dof_div_array.shape}")
--> [248](file:///C:/Users/jtgrasb/AppData/Local/anaconda3/envs/wot_cpy3/Lib/site-packages/capytaine/bodies/hydrostatics.py:248) radiating_dof_normal = immersed_self.dof_normals(radiating_dof)
    249 z = immersed_mesh.quadrature_points[0][:,:,2]
    250 z_influenced_dof_div = influenced_dof[:, None, 2] + z * influenced_dof_div_array

File c:\Users\jtgrasb\AppData\Local\anaconda3\envs\wot_cpy3\Lib\site-packages\capytaine\bodies\hydrostatics.py:98, in _FloatingBodyHydrostaticsMixin.dof_normals(self, dof)
     94 def dof_normals(self, dof):
     95     """Returns dot product of the surface face normals and DOF.
     96     Shape: (nb_faces,)
     97     """
---> [98](file:///C:/Users/jtgrasb/AppData/Local/anaconda3/envs/wot_cpy3/Lib/site-packages/capytaine/bodies/hydrostatics.py:98)     return np.sum(self.mesh.faces_normals * dof, axis=1)

TypeError: unsupported operand type(s) for *: 'float' and 'DofOnSubmesh'

@mancellin

Copy link
Copy Markdown
Author

It is a bug. Working on a fix: capytaine/capytaine#936.

I can propose a workaround, but maybe since there seems to be several issues with local/global rigid body dofs in v3, the simplest solution would be to enforce the use of Capytaine 2.3.1 for the LUPA example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants