forked from 1b-agent/fieldcraft-upstream
model: ties between surfaces
A tie names a main and a secondary face set and an optional position tolerance; it refers to its sets, round-trips, is bounded in number and checked for solid faces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
a91ac5baf9
commit
8d2fa980a5
4 files changed
+139
-1
No files matched your search
@@ -0,0 +1,110 @@
|
||||
//! Tie: two surfaces of non-matching meshes bonded together.
|
||||
//!
|
||||
//! A tie names a `main` and a `secondary` surface, sets of element faces. The solvers make every
|
||||
//! node of the secondary surface follow the main surface under it (the implicit solver by mortar
|
||||
//! constraints with dual shape functions, so a constant stress crosses the tie exactly). Nodes of
|
||||
//! the secondary surface farther than the `position_tolerance` from the main surface stay free
|
||||
//! (by default half the thickness of the main element under them).
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Error, Model, Result, context, insert_new, remove_existing, replace_existing, require};
|
||||
|
||||
/// Most ties in a model.
|
||||
pub const MAX_TIES: usize = 1000;
|
||||
|
||||
/// A tie definition.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Tie {
|
||||
pub main: String,
|
||||
pub secondary: String,
|
||||
/// How far a secondary node may lie from the main surface and still be tied (`None`: half
|
||||
/// the thickness of the main element under it).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub position_tolerance: Option<f64>,
|
||||
}
|
||||
|
||||
impl Tie {
|
||||
pub fn new(main: &str, secondary: &str) -> Tie {
|
||||
Tie { main: main.to_string(), secondary: secondary.to_string(), position_tolerance: None }
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
if self.main == self.secondary {
|
||||
return Err(Error::Invalid(format!("a tie needs two different surfaces, got `{}` twice", self.main)));
|
||||
}
|
||||
if let Some(t) = self.position_tolerance
|
||||
&& !(t > 0.0 && t.is_finite())
|
||||
{
|
||||
return Err(Error::Invalid(format!("a tie's position tolerance is a positive length, got {t}")));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn ties(&self) -> &std::collections::BTreeMap<String, Tie> {
|
||||
&self.ties
|
||||
}
|
||||
|
||||
pub fn tie(&self, name: &str) -> Option<&Tie> {
|
||||
self.ties.get(name)
|
||||
}
|
||||
|
||||
fn check_tie(&self, name: &str, t: &Tie) -> Result<()> {
|
||||
t.validate().map_err(|e| context("tie", name, e))?;
|
||||
for set in [&t.main, &t.secondary] {
|
||||
require(&self.sets, "set", set).map_err(|e| context("tie", name, e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_tie(&mut self, name: &str, tie: Tie) -> Result<()> {
|
||||
self.check_tie(name, &tie)?;
|
||||
if self.ties.len() >= MAX_TIES {
|
||||
return Err(Error::Limit(format!("a model holds at most {MAX_TIES} ties")));
|
||||
}
|
||||
insert_new(&mut self.ties, "tie", name, tie)
|
||||
}
|
||||
|
||||
pub fn replace_tie(&mut self, name: &str, tie: Tie) -> Result<()> {
|
||||
self.check_tie(name, &tie)?;
|
||||
replace_existing(&mut self.ties, "tie", name, tie)
|
||||
}
|
||||
|
||||
pub fn remove_tie(&mut self, name: &str) -> Result<()> {
|
||||
remove_existing(&mut self.ties, "tie", name).map(|_| ())
|
||||
}
|
||||
|
||||
/// The ties that name a set, as ``tie `name` `` strings.
|
||||
pub(crate) fn tie_users<'a>(&'a self, set: &'a str) -> impl Iterator<Item = String> + 'a {
|
||||
self.ties.iter().filter(move |(_, t)| t.main == set || t.secondary == set).map(|(n, _)| format!("tie `{n}`"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Set;
|
||||
|
||||
#[test]
|
||||
fn ties_validate_refer_to_sets_and_round_trip() {
|
||||
let mut m = Model::default();
|
||||
for s in ["a", "b"] {
|
||||
m.add_set(s, Set { nodes: vec![1], ..Set::default() }).unwrap();
|
||||
}
|
||||
m.add_tie("glue", Tie::new("a", "b")).unwrap();
|
||||
assert!(matches!(m.remove_set("a"), Err(Error::InUse(_))), "the tie refers to the set");
|
||||
assert!(m.add_tie("glue", Tie::new("a", "b")).is_err(), "names stay unique");
|
||||
assert!(m.add_tie("self", Tie::new("a", "a")).is_err());
|
||||
assert!(m.add_tie("far", Tie { position_tolerance: Some(-1.0), ..Tie::new("a", "b") }).is_err());
|
||||
assert!(matches!(m.add_tie("missing", Tie::new("a", "c")), Err(Error::NotFound(_))));
|
||||
let text = serde_json::to_string(&m).unwrap();
|
||||
let back: Model = serde_json::from_str(&text).unwrap();
|
||||
assert_eq!(back.ties(), m.ties());
|
||||
m.replace_tie("glue", Tie { position_tolerance: Some(0.1), ..Tie::new("b", "a") }).unwrap();
|
||||
m.remove_tie("glue").unwrap();
|
||||
assert!(m.remove_tie("glue").is_err());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user