Skip to content

Commit b498e4e

Browse files
committed
rework the flowcell logic and added a docs/example/flowcell_sampling.py
1 parent 6aadf22 commit b498e4e

4 files changed

Lines changed: 530 additions & 23 deletions

File tree

FlowCyPy/cpp/fluidics/flow_cell/flow_cell.cpp

Lines changed: 193 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ FlowCell::FlowCell(
99
int N_terms,
1010
int n_int,
1111
const std::string& event_scheme,
12+
const std::string& transverse_sampling_scheme,
1213
bool perfectly_aligned
1314
)
1415
: width(width),
@@ -20,34 +21,72 @@ FlowCell::FlowCell(
2021
N_terms(N_terms),
2122
n_int(n_int),
2223
event_scheme(event_scheme),
24+
transverse_sampling_scheme(transverse_sampling_scheme),
2325
perfectly_aligned(perfectly_aligned)
2426
{
27+
this->validate_physical_parameters();
2528
this->validate_event_scheme();
29+
this->validate_transverse_sampling_scheme();
2630
this->initialize();
2731
}
2832

2933
std::tuple<std::vector<double>, std::vector<double>, std::vector<double>>
3034
FlowCell::sample_transverse_profile(int n_samples) const {
35+
if (n_samples < 0) {
36+
throw std::runtime_error("n_samples must be non negative.");
37+
}
38+
3139
std::vector<double> y_samples;
3240
std::vector<double> z_samples;
3341
std::vector<double> velocity_samples;
3442

35-
y_samples.reserve(n_samples);
36-
z_samples.reserve(n_samples);
37-
velocity_samples.reserve(n_samples);
43+
y_samples.reserve(static_cast<std::size_t>(n_samples));
44+
z_samples.reserve(static_cast<std::size_t>(n_samples));
45+
velocity_samples.reserve(static_cast<std::size_t>(n_samples));
3846

3947
std::random_device random_device;
4048
std::mt19937 generator(random_device());
49+
4150
std::uniform_real_distribution<double> y_distribution(-sample.width / 2.0, sample.width / 2.0);
4251
std::uniform_real_distribution<double> z_distribution(-sample.height / 2.0, sample.height / 2.0);
52+
std::uniform_real_distribution<double> acceptance_distribution(0.0, 1.0);
4353

4454
for (int sample_index = 0; sample_index < n_samples; ++sample_index) {
45-
const double y = perfectly_aligned ? 0.0 : y_distribution(generator);
46-
const double z = perfectly_aligned ? 0.0 : z_distribution(generator);
55+
double y = 0.0;
56+
double z = 0.0;
57+
double velocity = u_center;
58+
59+
if (!perfectly_aligned) {
60+
bool accepted_sample = false;
61+
62+
while (!accepted_sample) {
63+
y = y_distribution(generator);
64+
z = z_distribution(generator);
65+
velocity = this->get_velocity(y, z, dpdx);
66+
67+
if (transverse_sampling_scheme == "uniform-random") {
68+
accepted_sample = true;
69+
} else if (transverse_sampling_scheme == "velocity-weighted") {
70+
const double acceptance_probability = velocity / u_center;
71+
72+
if (
73+
acceptance_probability >= 1.0 ||
74+
acceptance_distribution(generator) <= acceptance_probability
75+
) {
76+
accepted_sample = true;
77+
}
78+
} else {
79+
throw std::runtime_error(
80+
"Unsupported transverse_sampling_scheme '" + transverse_sampling_scheme +
81+
"'. Expected one of: 'velocity-weighted', 'uniform-random'."
82+
);
83+
}
84+
}
85+
}
4786

4887
y_samples.push_back(y);
4988
z_samples.push_back(z);
50-
velocity_samples.push_back(this->get_velocity(y, z, dpdx));
89+
velocity_samples.push_back(velocity);
5190
}
5291

5392
return std::make_tuple(y_samples, z_samples, velocity_samples);
@@ -70,20 +109,33 @@ void FlowCell::initialize() {
70109
throw std::runtime_error("Computed center velocity is non positive.");
71110
}
72111

73-
const double sample_area = sample_volume_flow / u_center;
74-
const double sample_height = std::sqrt(sample_area * height / width);
75-
const double sample_width = (width / height) * sample_height;
76-
const double average_flow_speed_sample = sample_volume_flow / sample_area;
112+
const double sample_core_scale = this->compute_sample_core_scale();
113+
const double sample_width = sample_core_scale * width;
114+
const double sample_height = sample_core_scale * height;
115+
const double sample_area = sample_width * sample_height;
116+
const double sample_average_flow_speed = sample_volume_flow / sample_area;
117+
const double sample_max_flow_speed = this->get_velocity(0.0, 0.0, dpdx);
77118

78119
this->sample = FluidRegion(
79120
sample_height,
80121
sample_width,
81122
sample_volume_flow,
82-
u_center,
83-
average_flow_speed_sample
123+
sample_max_flow_speed,
124+
sample_average_flow_speed
84125
);
85126

86-
this->sheath = FluidRegion(height, width, sheath_volume_flow);
127+
const double sheath_area = area - sample_area;
128+
const double sheath_average_flow_speed = sheath_area > 0.0
129+
? sheath_volume_flow / sheath_area
130+
: 0.0;
131+
132+
this->sheath = FluidRegion(
133+
height,
134+
width,
135+
sheath_volume_flow,
136+
u_center,
137+
sheath_average_flow_speed
138+
);
87139
}
88140

89141
void FlowCell::validate_event_scheme() const {
@@ -99,6 +151,99 @@ void FlowCell::validate_event_scheme() const {
99151
}
100152
}
101153

154+
void FlowCell::validate_transverse_sampling_scheme() const {
155+
if (
156+
transverse_sampling_scheme != "velocity-weighted" &&
157+
transverse_sampling_scheme != "uniform-random"
158+
) {
159+
throw std::runtime_error(
160+
"Unsupported transverse_sampling_scheme '" + transverse_sampling_scheme +
161+
"'. Expected one of: 'velocity-weighted', 'uniform-random'."
162+
);
163+
}
164+
}
165+
166+
void FlowCell::validate_physical_parameters() const {
167+
if (width <= 0.0) {
168+
throw std::runtime_error("width must be strictly positive.");
169+
}
170+
171+
if (height <= 0.0) {
172+
throw std::runtime_error("height must be strictly positive.");
173+
}
174+
175+
if (sample_volume_flow < 0.0) {
176+
throw std::runtime_error("sample_volume_flow must be non negative.");
177+
}
178+
179+
if (sheath_volume_flow < 0.0) {
180+
throw std::runtime_error("sheath_volume_flow must be non negative.");
181+
}
182+
183+
if (sample_volume_flow + sheath_volume_flow <= 0.0) {
184+
throw std::runtime_error("The total volume flow must be strictly positive.");
185+
}
186+
187+
if (viscosity <= 0.0) {
188+
throw std::runtime_error("viscosity must be strictly positive.");
189+
}
190+
191+
if (N_terms <= 0) {
192+
throw std::runtime_error("N_terms must be strictly positive.");
193+
}
194+
195+
if (n_int < 2) {
196+
throw std::runtime_error("n_int must be greater than or equal to 2.");
197+
}
198+
}
199+
200+
double FlowCell::compute_sample_core_scale() const {
201+
if (sample_volume_flow == 0.0) {
202+
return 0.0;
203+
}
204+
205+
if (sheath_volume_flow == 0.0) {
206+
return 1.0;
207+
}
208+
209+
const double full_channel_flow = this->compute_region_flow(width, height, dpdx);
210+
211+
if (full_channel_flow <= 0.0) {
212+
throw std::runtime_error("Computed full channel flow is non positive.");
213+
}
214+
215+
if (sample_volume_flow > full_channel_flow) {
216+
throw std::runtime_error("sample_volume_flow exceeds the computed full channel flow.");
217+
}
218+
219+
double lower_scale = 0.0;
220+
double upper_scale = 1.0;
221+
222+
constexpr int maximum_iterations = 80;
223+
constexpr double relative_tolerance = 1e-10;
224+
225+
for (int iteration = 0; iteration < maximum_iterations; ++iteration) {
226+
const double middle_scale = 0.5 * (lower_scale + upper_scale);
227+
const double region_width = middle_scale * width;
228+
const double region_height = middle_scale * height;
229+
const double region_flow = this->compute_region_flow(region_width, region_height, dpdx);
230+
231+
const double relative_error = std::abs(region_flow - sample_volume_flow) / sample_volume_flow;
232+
233+
if (relative_error < relative_tolerance) {
234+
return middle_scale;
235+
}
236+
237+
if (region_flow < sample_volume_flow) {
238+
lower_scale = middle_scale;
239+
} else {
240+
upper_scale = middle_scale;
241+
}
242+
}
243+
244+
return 0.5 * (lower_scale + upper_scale);
245+
}
246+
102247
double FlowCell::get_velocity(double y, double z, double dpdx_local) const {
103248
double velocity = 0.0;
104249
const double prefactor =
@@ -117,11 +262,36 @@ double FlowCell::get_velocity(double y, double z, double dpdx_local) const {
117262
return prefactor * velocity;
118263
}
119264

120-
double FlowCell::compute_channel_flow(double dpdx_input) const {
121-
const double y_min = -width / 2.0;
122-
const double y_max = width / 2.0;
123-
const double z_min = -height / 2.0;
124-
const double z_max = height / 2.0;
265+
double FlowCell::compute_region_flow(
266+
const double region_width,
267+
const double region_height,
268+
const double dpdx_input
269+
) const
270+
{
271+
if (region_width < 0.0) {
272+
throw std::runtime_error("region_width must be non negative.");
273+
}
274+
275+
if (region_height < 0.0) {
276+
throw std::runtime_error("region_height must be non negative.");
277+
}
278+
279+
if (region_width > width) {
280+
throw std::runtime_error("region_width cannot exceed the channel width.");
281+
}
282+
283+
if (region_height > height) {
284+
throw std::runtime_error("region_height cannot exceed the channel height.");
285+
}
286+
287+
if (region_width == 0.0 || region_height == 0.0) {
288+
return 0.0;
289+
}
290+
291+
const double y_min = -region_width / 2.0;
292+
const double y_max = region_width / 2.0;
293+
const double z_min = -region_height / 2.0;
294+
const double z_max = region_height / 2.0;
125295

126296
const double dy = (y_max - y_min) / static_cast<double>(n_int - 1);
127297
const double dz = (z_max - z_min) / static_cast<double>(n_int - 1);
@@ -140,6 +310,10 @@ double FlowCell::compute_channel_flow(double dpdx_input) const {
140310
return sum * dy * dz;
141311
}
142312

313+
double FlowCell::compute_channel_flow(double dpdx_input) const {
314+
return this->compute_region_flow(width, height, dpdx_input);
315+
}
316+
143317
std::vector<double>
144318
FlowCell::sample_arrival_times_uniform_random(
145319
const std::size_t n_events,
@@ -250,4 +424,4 @@ FlowCell::sample_arrival_times(
250424
"Unsupported event_scheme '" + event_scheme +
251425
"'. Expected one of: 'uniform-random', 'linear', 'poisson'."
252426
);
253-
}
427+
}

0 commit comments

Comments
 (0)