Skip to content

Commit b958c7d

Browse files
committed
Expose geometry computation via the solver api
1 parent 4c9d4b1 commit b958c7d

File tree

12 files changed

+136
-60
lines changed

12 files changed

+136
-60
lines changed

gui/source/solver/API.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ void save_result(const BowResult& result, const std::string& path) {
5353
check_response(response);
5454
}
5555

56+
DiscreteLimbGeometry compute_geometry(const BowModel& model) {
57+
std::vector<uint8_t> data = json::to_msgpack(model);
58+
Response response = ffi::compute_geometry(data.data(), data.size());
59+
return parse_response<DiscreteLimbGeometry>(response);
60+
}
61+
5662
BowResult simulate_model(const BowModel& model, Mode mode, bool (*callback)(Mode, double)) {
5763
std::vector<uint8_t> data = json::to_msgpack(model);
5864
Response response = ffi::simulate_model(data.data(), data.size(), mode, callback);

gui/source/solver/API.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ BowResult load_result(const std::string& path);
3131

3232
void save_result(const BowResult& result, const std::string& path);
3333

34+
DiscreteLimbGeometry compute_geometry(const BowModel& model);
35+
3436
BowResult simulate_model(const BowModel& model, Mode mode, bool (*callback)(Mode, double));

gui/source/solver/BowResult.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ struct nlohmann::adl_serializer<std::optional<T>> {
2424
}
2525
};
2626

27+
struct DiscreteLimbGeometry {
28+
std::vector<double> n_eval;
29+
std::vector<double> w_eval;
30+
std::vector<double> h_eval;
31+
};
32+
33+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
34+
DiscreteLimbGeometry,
35+
n_eval,
36+
w_eval,
37+
h_eval
38+
)
39+
2740
struct LayerInfo {
2841
std::string name;
2942
};

gui/source/tests/main.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ TEST_CASE("save-result-file") {
3636
REQUIRE_THROWS(save_result(result, TEST_DATA_DIR + "/temp/nonexistent/result.res"));
3737
}
3838

39+
TEST_CASE("compute-geometry") {
40+
BowModel model = new_model();
41+
DiscreteLimbGeometry geometry = compute_geometry(model);
42+
}
43+
3944
TEST_CASE("simulate-model") {
4045
BowModel model = new_model();
4146
BowResult result = simulate_model(model, Mode::Dynamic, [](Mode mode, double progress) {

rust/virtualbow/src/geometry.rs

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use iter_num_tools::lin_space;
22
use itertools::Itertools;
33
use nalgebra::{DMatrix, DVector, SVector, vector};
4+
use serde::{Deserialize, Serialize};
45
use crate::errors::ModelError;
56
use crate::input::{BowModel, HandleReference};
67
use crate::profile::profile::{CurvePoint, ProfileCurve};
@@ -13,6 +14,26 @@ pub struct LimbGeometry {
1314
pub section: LayeredCrossSection, // Limb cross sections
1415
}
1516

17+
// TODO: Return values s_nodes, u_node might not be needed if the evaluation works properly
18+
#[derive(Serialize, Deserialize, Default, PartialEq, Debug, Clone)]
19+
pub struct DiscreteLimbGeometry {
20+
pub segments: Vec<LinearBeamSegment>, // Linear beam segment properties
21+
pub n_nodes: Vec<f64>, // Relative lengths of the element nodes
22+
pub s_nodes: Vec<f64>, // Arc lengths of the element nodes
23+
pub p_nodes: Vec<SVector<f64, 3>>, // Positions (x, y, φ) of the element nodes
24+
pub y_nodes: Vec<DVector<f64>>, // Layer bounds at nodes (y in cross section coordinates)
25+
26+
pub n_eval: Vec<f64>, // Relative lengths at which the limb quantities are evaluated
27+
pub s_eval: Vec<f64>, // Arc lengths at which the limb quantities are evaluated
28+
pub p_eval: Vec<SVector<f64, 3>>, // Positions (x, y, φ) of the evaluation points
29+
pub y_eval: Vec<DVector<f64>>, // Layer bounds at eval points (y in cross section coordinates)
30+
pub w_eval: Vec<f64>, // Widths at eval points
31+
pub h_eval: Vec<f64>, // Total heights at eval points
32+
33+
pub strain_eval: Vec<DMatrix<f64>>, // Strain evaluation matrices for each evaluation point
34+
pub stress_eval: Vec<DMatrix<f64>>, // Stress evaluation matrices for each evaluation point
35+
}
36+
1637
impl LimbGeometry {
1738
pub fn new(input: &BowModel) -> Result<Self, ModelError> {
1839
// Section properties according to layers, materials and alignment to the profile curve
@@ -59,14 +80,14 @@ impl LimbGeometry {
5980
pub fn discretize(&self, n_eval_points: usize, n_elements: usize) -> DiscreteLimbGeometry {
6081
// Arc lengths and normalized positions along the profile where the element nodes are placed
6182
let s_nodes = lin_space(self.profile.s_start()..=self.profile.s_end(), n_elements + 1).collect_vec();
62-
let p_nodes = s_nodes.iter().map(|&s| self.profile.normalize(s)).collect_vec();
63-
let u_nodes = s_nodes.iter().map(|&s| self.profile.point(s)).collect_vec();
64-
let y_nodes = p_nodes.iter().map(|&p| self.section.layer_bounds(p).0).collect_vec();
83+
let n_nodes = s_nodes.iter().map(|&s| self.profile.normalize(s)).collect_vec();
84+
let p_nodes = s_nodes.iter().map(|&s| self.profile.point(s)).collect_vec();
85+
let y_nodes = n_nodes.iter().map(|&n| self.section.layer_bounds(n).0).collect_vec();
6586

6687
// Equidistant evaluation points along the length of the limb
6788
let s_eval = lin_space(self.profile.s_start()..=self.profile.s_end(), n_eval_points).collect_vec();
68-
let p_eval = s_eval.iter().map(|&s| self.profile.normalize(s)).collect_vec();
69-
let y_eval = p_eval.iter().map(|&p| self.section.layer_bounds(p).0).collect_vec();
89+
let n_eval = s_eval.iter().map(|&s| self.profile.normalize(s)).collect_vec();
90+
let y_eval = n_eval.iter().map(|&n| self.section.layer_bounds(n).0).collect_vec();
7091

7192
let segments = s_nodes.iter().tuple_windows().enumerate().map(|(i, (&s0, &s1))| {
7293
// TODO: Better solution for numerical issues?
@@ -86,45 +107,47 @@ impl LimbGeometry {
86107
LinearBeamSegment::new(&self.profile, &self.section, s0, s1, &s_eval)
87108
}).collect();
88109

89-
let strain_eval = p_eval.iter().map(|&p| self.section.strain_eval(p)).collect();
90-
let stress_eval = p_eval.iter().map(|&p| self.section.stress_eval(p)).collect();
110+
let p_eval = s_eval.iter().map(|&s| self.profile.point(s)).collect();
111+
let w_eval = n_eval.iter().map(|&n| self.section.width(n)).collect();
112+
let h_eval = n_eval.iter().map(|&n| self.section.height(n)).collect();
91113

92-
let position = s_eval.iter().map(|&s| self.profile.point(s)).collect();
93-
let width = p_eval.iter().map(|&p| self.section.width(p)).collect();
94-
let height = p_eval.iter().map(|&p| self.section.height(p)).collect();
114+
let strain_eval = n_eval.iter().map(|&n| self.section.strain_eval(n)).collect();
115+
let stress_eval = n_eval.iter().map(|&n| self.section.stress_eval(n)).collect();
95116

96117
DiscreteLimbGeometry {
97118
segments,
119+
n_nodes,
98120
s_nodes,
99-
u_nodes,
121+
p_nodes,
100122
y_nodes,
123+
n_eval,
101124
s_eval,
102125
y_eval,
103126
strain_eval,
104127
stress_eval,
105-
position,
106-
width,
107-
height
128+
p_eval,
129+
w_eval,
130+
h_eval
108131
}
109132
}
110133
}
111134

112-
// TODO: Return values s_nodes, u_node might not be needed if the evaluation works properly
113-
pub struct DiscreteLimbGeometry {
114-
pub segments: Vec<LinearBeamSegment>, // Linear beam segment properties
115-
pub s_nodes: Vec<f64>, // Arc lengths of the element nodes
116-
pub u_nodes: Vec<SVector<f64, 3>>, // Positions (x, y, φ) of the element nodes
117-
pub y_nodes: Vec<DVector<f64>>, // Layer bounds at nodes (y in cross section coordinates)
135+
impl TryInto<Vec<u8>> for DiscreteLimbGeometry {
136+
type Error = ModelError;
118137

119-
pub s_eval: Vec<f64>, // Arc lengths at which the limb quantities are evaluated (positions, forces, ...)
120-
pub y_eval: Vec<DVector<f64>>, // Layer bounds at eval points (y in cross section coordinates)
121-
pub strain_eval: Vec<DMatrix<f64>>, // Strain evaluation matrices for each evaluation point
122-
pub stress_eval: Vec<DMatrix<f64>>, // Stress evaluation matrices for each evaluation point
138+
// Conversion into MsgPack byte array
139+
fn try_into(self) -> Result<Vec<u8>, Self::Error> {
140+
rmp_serde::to_vec_named(&self).map_err(ModelError::OutputEncodeMsgPackError) // TODO: Bett error type?
141+
}
142+
}
143+
144+
impl TryFrom<&[u8]> for DiscreteLimbGeometry {
145+
type Error = ModelError;
123146

124-
// TODO: Unify with rest
125-
pub position: Vec<SVector<f64, 3>>,
126-
pub width: Vec<f64>,
127-
pub height: Vec<f64>
147+
// Conversion from MsgPack byte array
148+
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
149+
rmp_serde::from_slice(value).map_err(ModelError::OutputDecodeMsgPackError)
150+
}
128151
}
129152

130153
#[cfg(test)]

rust/virtualbow/src/sections/section.rs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -195,39 +195,40 @@ impl LayeredCrossSection {
195195
&self.layers
196196
}
197197

198-
// Computes the layer boundaries at arc length s. Also returns the heights as a byproduct.
199-
pub fn layer_bounds(&self, p: f64) -> (DVector<f64>, DVector<f64>) {
200-
let h = self.layer_heights(p);
198+
// Computes the layer boundaries at normalized length n in [0, 1].
199+
// Also returns the layer heights as a byproduct.
200+
pub fn layer_bounds(&self, n: f64) -> (DVector<f64>, DVector<f64>) {
201+
let h = self.layer_heights(n);
201202
(&self.stacking*&h, h)
202203
}
203204

204-
// Computes the section boundaries (belly, back) at arc length s.
205+
// Computes the section boundaries (belly, back) at normalized length n.
205206
// Equivalent to first and last layer bound.
206-
pub fn section_bounds(&self, p: f64) -> (f64, f64) {
207-
let (y, _) = self.layer_bounds(p);
207+
pub fn section_bounds(&self, n: f64) -> (f64, f64) {
208+
let (y, _) = self.layer_bounds(n);
208209
(y[0], y[y.len() - 1])
209210
}
210211

211-
// Evaluates the heights of the individual layers at arc length s and returns them as a vector
212-
pub fn layer_heights(&self, p: f64) -> DVector<f64> {
212+
// Evaluates the heights of the individual layers at normalized length n and returns them as a vector
213+
pub fn layer_heights(&self, n: f64) -> DVector<f64> {
213214
DVector::<f64>::from_fn(self.layers.len(), |i, _| {
214-
self.layers[i].height.value(p, Extrapolation::Constant)
215+
self.layers[i].height.value(n, Extrapolation::Constant)
215216
})
216217
}
217218
}
218219

219220
impl CrossSection for LayeredCrossSection {
220-
fn ρA(&self, p: f64) -> f64 {
221-
let w = self.width.value(p, Extrapolation::Constant);
221+
fn ρA(&self, n: f64) -> f64 {
222+
let w = self.width.value(n, Extrapolation::Constant);
222223
self.layers.iter().map(|layer| {
223-
let h = layer.height.value(p, Extrapolation::Constant);
224+
let h = layer.height.value(n, Extrapolation::Constant);
224225
layer.material.density*w*h
225226
}).sum()
226227
}
227228

228-
fn ρI(&self, p: f64) -> f64 {
229-
let w = self.width(p);
230-
let (y, h) = self.layer_bounds(p);
229+
fn ρI(&self, n: f64) -> f64 {
230+
let w = self.width(n);
231+
let (y, h) = self.layer_bounds(n);
231232

232233
self.layers.iter().enumerate().map(|(i, layer)| {
233234
let A = w*h[i];
@@ -237,12 +238,12 @@ impl CrossSection for LayeredCrossSection {
237238
}).sum()
238239
}
239240

240-
fn C(&self, p: f64) -> SMatrix<f64, 3, 3> {
241-
let w = self.width(p);
242-
let (y, h) = self.layer_bounds(p);
241+
fn C(&self, n: f64) -> SMatrix<f64, 3, 3> {
242+
let w = self.width(n);
243+
let (y, h) = self.layer_bounds(n);
243244

244245
let Cee = self.layers.iter().map(|layer| {
245-
let h = layer.height.value(p, Extrapolation::Constant);
246+
let h = layer.height.value(n, Extrapolation::Constant);
246247
layer.material.youngs_modulus*w*h
247248
}).sum();
248249

@@ -260,7 +261,7 @@ impl CrossSection for LayeredCrossSection {
260261
}).sum();
261262

262263
let Cγγ = self.layers.iter().map(|layer| {
263-
let h = layer.height.value(p, Extrapolation::Constant);
264+
let h = layer.height.value(n, Extrapolation::Constant);
264265
layer.material.shear_modulus*w*h
265266
}).sum();
266267

@@ -271,19 +272,19 @@ impl CrossSection for LayeredCrossSection {
271272
]
272273
}
273274

274-
fn width(&self, p: f64) -> f64 {
275-
self.width.value(p, Extrapolation::Constant)
275+
fn width(&self, n: f64) -> f64 {
276+
self.width.value(n, Extrapolation::Constant)
276277
}
277278

278279
// Total height as the sum of all layers
279-
fn height(&self, p: f64) -> f64 {
280-
self.layers.iter().map(|layer| layer.height.value(p, Extrapolation::Constant)).sum()
280+
fn height(&self, n: f64) -> f64 {
281+
self.layers.iter().map(|layer| layer.height.value(n, Extrapolation::Constant)).sum()
281282
}
282283

283284
// Strain evaluation matrix. Produces normal strains at back and belly of each layer.
284-
fn strain_eval(&self, p: f64) -> DMatrix<f64> {
285+
fn strain_eval(&self, n: f64) -> DMatrix<f64> {
285286
// Layer bounds are the points of interest
286-
let (y, _) = self.layer_bounds(p);
287+
let (y, _) = self.layer_bounds(n);
287288

288289
// Normal strain is epsilon - kappa*y
289290
DMatrix::from_fn(2*self.layers.len(), 3, |i, j| {
@@ -297,9 +298,9 @@ impl CrossSection for LayeredCrossSection {
297298
}
298299

299300
// Stress evaluation matrix. Produces stresses at back and belly of each layer.
300-
fn stress_eval(&self, p: f64) -> DMatrix<f64> {
301+
fn stress_eval(&self, n: f64) -> DMatrix<f64> {
301302
// Layer bounds are the points of interest
302-
let (y, _) = self.layer_bounds(p);
303+
let (y, _) = self.layer_bounds(n);
303304

304305
// Normal stress is E*(epsilon - kappa*y)
305306
DMatrix::from_fn(2*self.layers.len(), 3, |i, j| {

rust/virtualbow/src/simulation.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ impl<'a> Simulation<'a> {
7777

7878
let mut system = System::new();
7979

80-
let limb_nodes: Vec<Node> = geometry.u_nodes.iter().enumerate().map(|(i, u)| {
80+
let limb_nodes: Vec<Node> = geometry.p_nodes.iter().enumerate().map(|(i, u)| {
8181
system.create_node(u, &[i != 0; 3]) // First node is fixed, all others
8282
}).collect();
8383

@@ -236,9 +236,9 @@ impl<'a> Simulation<'a> {
236236
let common = Common {
237237
limb: LimbInfo {
238238
length: simulation.geometry.s_eval.clone(),
239-
position: simulation.geometry.position.clone(),
240-
width: simulation.geometry.width.clone(),
241-
height: simulation.geometry.height.clone(),
239+
position: simulation.geometry.p_eval.clone(),
240+
width: simulation.geometry.w_eval.clone(),
241+
height: simulation.geometry.h_eval.clone(),
242242
bounds: simulation.geometry.y_eval.iter().map(|y| y.data.clone().into()).collect(), // TODO: Uglyyy
243243
},
244244
layers,

rust/virtualbow_ffi/src/api.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::path::Path;
22
use virtualbow::errors::ModelError;
3+
use virtualbow::geometry::LimbGeometry;
34
use virtualbow::input::BowModel;
45
use virtualbow::input::BowModelVersion;
56
use virtualbow::output::BowResult;
@@ -47,6 +48,15 @@ pub fn save_result<P>(data: &[u8], path: P) -> Result<(), String>
4748
result.save(path).map_err(|e| e.to_string())
4849
}
4950

51+
pub fn compute_geometry(data: &[u8]) -> Result<Vec<u8>, String> {
52+
let model = BowModel::try_from(data).map_err(|e| e.to_string())?;
53+
let geometry = LimbGeometry::new(&model).map_err(|e| e.to_string())?;
54+
let discretized = geometry.discretize(model.settings.num_limb_eval_points, model.settings.num_limb_elements);
55+
let data = discretized.try_into().map_err(|e: ModelError| e.to_string())?;
56+
57+
Ok(data)
58+
}
59+
5060
pub fn simulate_model<F>(data: &[u8], mode: SimulationMode, callback: F) -> Result<Vec<u8>, String>
5161
where F: Fn(SimulationMode, f64) -> bool
5262
{

rust/virtualbow_ffi/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,17 @@ pub unsafe extern "C" fn save_result(data: *const u8, size: usize, path: *const
120120
}
121121
}
122122

123+
#[no_mangle]
124+
pub unsafe extern "C" fn compute_geometry(data: *const u8, size: usize) -> Response {
125+
let data = std::slice::from_raw_parts(data, size);
126+
let result = api::compute_geometry(&data);
127+
128+
match result {
129+
Ok(res) => Response::data(res),
130+
Err(msg) => Response::error(msg),
131+
}
132+
}
133+
123134
#[repr(C)]
124135
pub enum Mode {
125136
Static,

rust/virtualbow_ffi/virtualbow.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ Response load_result(const char *path);
3333

3434
Response save_result(const uint8_t *data, uintptr_t size, const char *path);
3535

36+
Response compute_geometry(const uint8_t *data, uintptr_t size);
37+
3638
Response simulate_model(const uint8_t *data, uintptr_t size, Mode mode, bool (*callback)(Mode, double));
3739

3840
void free_response(Response response);

0 commit comments

Comments
 (0)