Best Practices
Discipline Development
Always Override Compute Methods
The Compute() and ComputePartials() (explicit) or ComputeResiduals() and SolveResiduals() (implicit) methods have no default implementations:
}
}
}
std::map< std::string, philote::Variable > Variables
Definition variable.h:404
Call Parent Initialize
When overriding Initialize(), always call the parent version first:
void Initialize() override {
ExplicitDiscipline::Initialize();
AddOption("my_option", "float");
}
Declare All Partials
Use DeclarePartials() for every gradient you compute:
void SetupPartials() override {
DeclarePartials("f", "x");
DeclarePartials("f", "y");
DeclarePartials("residual", "output");
}
Variable Handling
Check Variable Dimensions
Always verify shapes match your expectations:
const auto& vec = inputs.at("vec");
assert(vec.Size() == expected_size);
for (size_t i = 0; i < vec.Size(); ++i) {
outputs.at("result")(i) = process(vec(i));
}
}
Use Meaningful Units
Provide physical units for all variables:
AddInput("pressure", {1}, "Pa");
AddInput("temperature", {1}, "K");
AddInput("length", {1}, "m");
AddInput("p", {1}, "");
Use Type-Safe Access
Prefer .at() over [] for bounds checking:
double x = inputs.at("x")(0);
double x = inputs["x"](0);
Error Handling
Wrap Discipline Logic
Wrap computations in try-catch blocks:
try {
double x = inputs.at("x")(0);
if (x < 0) {
throw std::runtime_error("x must be non-negative");
}
outputs.at("y")(0) = std::sqrt(x);
} catch (const std::exception& e) {
std::cerr << "Compute error: " << e.what() << std::endl;
throw;
}
}
Validate Inputs
Check input validity before computation:
double divisor = inputs.at("divisor")(0);
if (std::abs(divisor) < 1e-14) {
throw std::runtime_error("Division by zero");
}
outputs.at("result")(0) = inputs.at("numerator")(0) / divisor;
}
Client Usage
Follow Initialization Sequence
Always initialize clients in the correct order:
client.ConnectChannel(channel);
client.GetInfo();
client.Setup();
client.GetVariableDefinitions();
client.GetPartialDefinitions();
client.GetVariableDefinitions();
Sequential Operations
Don't call client methods concurrently:
std::thread t1([&]() { client.ComputeFunction(inputs1); });
std::thread t2([&]() { client.ComputeFunction(inputs2); });
Reuse Clients
Create clients once and reuse:
for (int i = 0; i < 1000; ++i) {
}
for (int i = 0; i < 1000; ++i) {
}
void GetInfo()
Get the discipline info.
void GetVariableDefinitions()
Get the variable definitions from the server.
void Setup()
Setup the discipline.
Client class for calling a remote explicit discipline.
Definition explicit.h:418
Variables ComputeFunction(const Variables &inputs)
Calls the remote analysis server function evaluation via gRPC.
void ConnectChannel(std::shared_ptr< grpc::ChannelInterface > channel)
Connects the client stub to a gRPC channel.
Documentation
Document Assumptions
Clearly document assumptions in your code:
Comment Complex Logic
Add comments for non-obvious computations:
double x = inputs.at("x")(0);
double inner = g(x);
double df_dg = f_prime(inner);
double dg_dx = g_prime(x);
partials[{"f", "x"}](0) = df_dg * dg_dx;
}
std::map< std::pair< std::string, std::string >, philote::Variable > Partials
Definition variable.h:405
Limitations
No Concurrent RPC Support
The library currently supports only one RPC call at a time:
std::thread t1([&]() {
client1.ComputeFunction(inputs);
});
std::thread t2([&]() {
server.ProcessRequest();
});
Single Server Instance
Each discipline should be hosted on a single server instance:
MyDiscipline discipline;
grpc::ServerBuilder builder;
discipline.RegisterServices(builder);
auto server = builder.BuildAndStart();
grpc::ServerBuilder builder1, builder2;
discipline.RegisterServices(builder1);
discipline.RegisterServices(builder2);
Sequential Client Operations
All client operations must be called sequentially:
std::async(std::launch::async, [&](){ client.
GetInfo(); });
std::async(std::launch::async, [&](){ client.
Setup(); });
No Auto-Reconnect
If the connection drops, create a new client:
try {
} catch (const std::exception& e) {
}
Variable Shape Constraints
Variable shapes are fixed after Setup():
void Setup() override {
AddInput("x", {10}, "m");
}
}
Performance Considerations
Minimize Data Transfers
Reduce the number of RPC calls:
for (int i = 0; i < 100; ++i) {
inputs["x"](0) = values[i];
}
A class for storing continuous and discrete variables.
Definition variable.h:85
Preallocate Variables
Preallocate variables instead of reallocating:
for (double val : values) {
inputs.at("x")(0) = val;
}
for (double val : values) {
inputs.at("x")(0) = val;
}
Use Efficient Data Structures
Choose appropriate shapes for your data:
AddInput("time_series", {10000}, "s");
AddInput("matrix", {100, 100}, "m");
AddInput("tensor", {10, 10, 10, 10, 10}, "m");
Common Pitfalls
Forgetting to Declare Partials
void SetupPartials() override {
}
partials[{"f", "x"}](0) = 2.0;
}
void SetupPartials() override {
DeclarePartials("f", "x");
}
Modifying Const Inputs
inputs.at("x")(0) = 5.0;
}
Incorrect Variable Types
Debugging Tips
Enable Verbose Logging
setenv("GRPC_VERBOSITY", "DEBUG", 1);
setenv("GRPC_TRACE", "all", 1);
Verify Variable Metadata
void Setup() override {
AddInput("x", {10}, "m");
for (const auto& var : var_meta()) {
std::cout << "Variable: " << var.name()
<< ", Size: " << var.shape(0)
<< ", Units: " << var.units() << std::endl;
}
}
Check Gradient Accuracy
Use finite differences to verify analytic gradients:
double eps = 1e-7;
double x = inputs.at("x")(0);
ComputePartials(inputs, partials);
double analytic = partials[{"f", "x"}](0);
inputs.at("x")(0) = x + eps;
Compute(inputs, out_plus);
inputs.at("x")(0) = x - eps;
Compute(inputs, out_minus);
double numeric = (out_plus.at("f")(0) - out_minus.at("f")(0)) / (2 * eps);
std::cout << "Analytic: " << analytic << ", Numeric: " << numeric << std::endl;
See Also