Philote-Cpp
C++ bindings for the Philote MDO standard
Loading...
Searching...
No Matches
Explicit Disciplines

Introduction

Explicit disciplines compute direct function evaluations (f(x) = y) and optionally their gradients. They are suitable for analyses where outputs can be computed directly from inputs without solving implicit equations.

Creating a Simple Explicit Discipline

Here's a minimal explicit discipline:

#include <explicit.h>
class SimpleFunction : public philote::ExplicitDiscipline {
private:
void Setup() override {
AddInput("x", {1}, "m");
AddOutput("y", {1}, "m");
}
void SetupPartials() override {
DeclarePartials("y", "x");
}
void Compute(const philote::Variables &inputs,
philote::Variables &outputs) override {
double x = inputs.at("x")(0);
outputs.at("y")(0) = 2.0 * x + 1.0;
}
void ComputePartials(const philote::Variables &inputs,
philote::Partials &partials) override {
partials[{"y", "x"}](0) = 2.0;
}
};
void AddInput(const std::string &name, const std::vector< int64_t > &shape, const std::string &units)
Declares an input.
virtual void SetupPartials()
Setup function that is called by the server when the client calls the setup RPC.
void AddOutput(const std::string &name, const std::vector< int64_t > &shape, const std::string &units)
Declares an output.
virtual void Setup()
Setup function that is called by the server when the client calls the setup RPC.
void DeclarePartials(const std::string &f, const std::string &x)
Declare a (set of) partial(s) for the discipline.
Explicit discipline class.
Definition explicit.h:263
virtual void Compute(const philote::Variables &inputs, philote::Variables &outputs)
Function evaluation for the discipline.
virtual void ComputePartials(const philote::Variables &inputs, Partials &partials)
Gradient evaluation for the discipline.
std::map< std::string, philote::Variable > Variables
Definition variable.h:404
std::map< std::pair< std::string, std::string >, philote::Variable > Partials
Definition variable.h:405

Multi-Input/Output Discipline

A more complex example with multiple inputs and outputs:

#include <explicit.h>
#include <cmath>
private:
void Setup() override {
AddInput("x", {1}, "m");
AddInput("y", {1}, "m");
AddOutput("f_xy", {1}, "m**2");
}
void SetupPartials() override {
DeclarePartials("f_xy", "x");
DeclarePartials("f_xy", "y");
}
void Compute(const philote::Variables &inputs,
philote::Variables &outputs) override {
double x = inputs.at("x")(0);
double y = inputs.at("y")(0);
outputs.at("f_xy")(0) = std::pow(x - 3.0, 2.0) + x * y +
std::pow(y + 4.0, 2.0) - 3.0;
}
void ComputePartials(const philote::Variables &inputs,
philote::Partials &partials) override {
double x = inputs.at("x")(0);
double y = inputs.at("y")(0);
partials[{"f_xy", "x"}](0) = 2.0 * x - 6.0 + y;
partials[{"f_xy", "y"}](0) = 2.0 * y + 8.0 + x;
}
};
Definition paraboloid_server.cpp:48

Discipline with Options

Disciplines can define configurable options:

class ConfigurableDiscipline : public philote::ExplicitDiscipline {
private:
double scale_factor_ = 1.0;
void Initialize() override {
AddOption("scale_factor", "float");
AddOption("enable_scaling", "bool");
}
void Configure() override {
// Configuration based on options set by client
// Options are available via discipline properties
}
void Setup() override {
AddInput("x", {1}, "m");
AddOutput("y", {1}, "m");
}
void Compute(const philote::Variables &inputs,
philote::Variables &outputs) override {
outputs.at("y")(0) = scale_factor_ * inputs.at("x")(0);
}
};
virtual void Initialize()
Initialize function that sets up available options.
virtual void Configure()
Configure function that is called after options are set.
void AddOption(const std::string &name, const std::string &type)
Add an option to the discipline.

Starting a Server

Once you've defined your discipline, create a server to host it:

#include <grpcpp/grpcpp.h>
int main() {
std::string address("localhost:50051");
Paraboloid discipline;
grpc::ServerBuilder builder;
builder.AddListeningPort(address, grpc::InsecureServerCredentials());
discipline.RegisterServices(builder);
std::unique_ptr<grpc::Server> server = builder.BuildAndStart();
std::cout << "Server listening on " << address << std::endl;
server->Wait();
return 0;
}
void RegisterServices(grpc::ServerBuilder &builder)
Registers all services with a gRPC channel.
int main()
Definition paraboloid_client.cpp:50

Required Methods

Setup()

Called once to define variables:

void Setup() override {
// Define all inputs
AddInput("name", {shape}, "units");
// Define all outputs
AddOutput("name", {shape}, "units");
}

Compute()

Must be implemented - performs the actual computation:

void Compute(const philote::Variables &inputs,
philote::Variables &outputs) override {
// Extract input values
double x = inputs.at("x")(0);
// Compute outputs
outputs.at("y")(0) = f(x);
}

SetupPartials() and ComputePartials()

Optional, but recommended for optimization:

void SetupPartials() override {
// Declare which gradients are available
DeclarePartials("output", "input");
}
void ComputePartials(const philote::Variables &inputs,
philote::Partials &partials) override {
// Compute gradients
partials[{"output", "input"}](0) = df_dx;
}

Lifecycle

The discipline lifecycle when a client connects:

  1. Initialize() - Define available options
  2. SetOptions() - Client sets option values
  3. Configure() - Process option values
  4. Setup() - Define variables
  5. SetupPartials() - Declare available gradients
  6. Compute() - Called for each function evaluation
  7. ComputePartials() - Called for each gradient evaluation

Best Practices

  1. Always override Compute(): No default implementation exists
  2. Call parent Initialize(): When overriding, call ExplicitDiscipline::Initialize() first
  3. Declare all partials: Use DeclarePartials() for every gradient you compute
  4. Use const for inputs: Input variables should not be modified
  5. Preallocate outputs: Framework preallocates based on Setup()
  6. Handle exceptions: Wrap computation in try-catch for robustness

Common Patterns

Vector Operations

void Setup() override {
AddInput("vec", {10}, "m");
AddOutput("scaled", {10}, "m");
}
void Compute(const philote::Variables &inputs,
philote::Variables &outputs) override {
for (size_t i = 0; i < 10; ++i) {
outputs.at("scaled")(i) = 2.0 * inputs.at("vec")(i);
}
}

Matrix Operations

void Setup() override {
AddInput("matrix", {3, 3}, "m");
AddOutput("determinant", {1}, "m**3");
}
void Compute(const philote::Variables &inputs,
philote::Variables &outputs) override {
// Access matrix elements with linear indexing
// Element [i,j] is at index i*cols + j
const auto& A = inputs.at("matrix");
outputs.at("determinant")(0) = ComputeDeterminant(A);
}

Handling Cancellation

For long-running computations, disciplines can detect and respond to client cancellations (timeouts, disconnects) by checking IsCancelled():

class LongComputation : public philote::ExplicitDiscipline {
void Compute(const philote::Variables &inputs,
philote::Variables &outputs) override {
for (int iter = 0; iter < 1000000; iter++) {
// Check cancellation periodically (every 1000 iterations)
if (iter % 1000 == 0 && IsCancelled()) {
throw std::runtime_error("Computation cancelled by client");
}
// ... perform expensive work ...
}
outputs.at("result")(0) = computed_value;
}
};
bool IsCancelled() const noexcept
Check if the current operation has been cancelled.

Key Points:

  • Server automatically detects cancellations - IsCancelled() is optional
  • Only use for computations that would benefit from early termination
  • Check periodically (not every iteration) to minimize overhead
  • Works across all client languages (Python, C++, etc.)
  • Throw exception or return early when cancelled

Client-Side:

client.SetRPCTimeout(std::chrono::milliseconds(5000)); // 5s timeout
try {
auto outputs = client.ComputeFunction(inputs);
} catch (const std::runtime_error& e) {
// Handle timeout/cancellation
}

Complete Examples

See the examples directory for complete working examples:

  • examples/paraboloid/ - Basic explicit discipline
  • examples/rosenbrock/ - Classic optimization test function

See Also