Janus 2.0.0
High-performance C++20 dual-mode numerical framework
Loading...
Searching...
No Matches
Branching Logic in Janus

The Problem

Standard C++ control flow (if/else, switch) doesn't work with symbolic types:

// ❌ DOESN'T WORK - condition can't be evaluated at graph-building time
template <typename Scalar>
Scalar bad_example(const Scalar& x) {
if (x < 0) { // ERROR with symbolic types!
return -x;
}
return x;
}

The Solution: janus::where()

Use janus::where(condition, true_val, false_val) for symbolic-compatible branching:

// ✅ WORKS - creates computational graph
template <typename Scalar>
Scalar good_example(const Scalar& x) {
return janus::where(x < 0.0, -x, x);
}
auto where(const Cond &cond, const T1 &if_true, const T2 &if_false)
Select values based on condition (ternary operator) Returns: cond ? if_true : if_false Supports mixed...
Definition Logic.hpp:43

Pattern Guide

1. Simple If-Else

// C++: if (x < 0) return -x; else return x;
return janus::where(x < 0.0, -x, x);

2. If-Else-If-Else (Using select - RECOMMENDED)

// C++: if (x < -1) return -1; else if (x > 1) return 1; else return x;
// ✅ Clean with select()
return janus::select({x < -1.0, x > 1.0},
{Scalar(-1.0), Scalar(1.0)},
x); // default
// ❌ Hard to read with nested where()
return janus::where(x < -1.0,
Scalar(-1.0),
janus::where(x > 1.0, Scalar(1.0), x));
Scalar select(const std::vector< CondType > &conditions, const std::vector< Scalar > &values, const Scalar &default_value)
Multi-way conditional selection (cleaner alternative to nested where).
Definition Logic.hpp:547

3. Switch-Case Logic (select shines here!)

// C++: switch (regime) { case 0: ... case 1: ... }
return janus::select({mach < 0.3, mach < 0.8, mach < 1.2},
{Scalar(0.02), Scalar(0.025), Scalar(0.05)},
Scalar(0.03)); // default

4. Complex Multi-Step Logic

When branches need multiple calculation steps, use helper functions:

// Helper functions for complex calculations
template <typename Scalar>
Scalar turbulent_flow(const Scalar& re, const Scalar& v) {
auto cf = 0.074 / janus::pow(re, 0.2);
auto correction = 1.0 + 0.144 * janus::pow(v / 343.0, 2.0);
return cf * correction;
}
template <typename Scalar>
Scalar laminar_flow(const Scalar& re) {
return 1.328 / janus::sqrt(re);
}
// Use in branching logic
template <typename Scalar>
Scalar skin_friction(const Scalar& re, const Scalar& v) {
return janus::where(re > 5e5,
turbulent_flow(re, v), // Multi-step calculation
laminar_flow(re)); // Simpler calculation
}
T sqrt(const T &x)
Computes the square root of a scalar.
Definition Arithmetic.hpp:46
T pow(const T &base, const T &exponent)
Computes the power function: base^exponent.
Definition Arithmetic.hpp:72

Important: Both branches are always evaluated in symbolic mode (that's how computational graphs work). The condition selects which result to use.

5. Multi-Variable Conditions

// Combine multiple conditions
Scalar is_stalled = alpha_abs > alpha_stall;
Scalar low_reynolds = reynolds < 1e5;
return janus::where(is_stalled && low_reynolds,
correction_value,
nominal_value);

API Reference

janus::where(condition, true_val, false_val)

Basic ternary selection. Use for simple if-else.

janus::select(conditions, values, default_value)

Multi-way selection. Cleaner than nested where() for switch-like logic.

  • conditions: Vector/list of conditions to check (in order)
  • values: Vector/list of values to return (same size as conditions)
  • default_value: Value if no condition matches

Returns the value corresponding to the first true condition, or default.

Key Takeaways

  1. Always use janus::where() for conditions involving template scalar types
  2. Nest where() calls for multi-way branching (if-else-if chains)
  3. Compute intermediate flags to make complex logic readable
  4. Works in both modes: numeric (evaluates immediately) and symbolic (builds graph)
  5. Enables autodiff: derivatives flow through all branches correctly

See Also