Philote-Cpp
C++ bindings for the Philote MDO standard
Loading...
Searching...
No Matches
implicit.h
Go to the documentation of this file.
1/*
2 Philote C++ Bindings
3
4 Copyright 2022-2025 Christopher A. Lupp
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17
18 This work has been cleared for public release, distribution unlimited, case
19 number: AFRL-2023-5716.
20
21 The views expressed are those of the authors and do not reflect the
22 official guidance or position of the United States Government, the
23 Department of Defense or of the United States Air Force.
24
25 Statement from DoD: The Appearance of external hyperlinks does not
26 constitute endorsement by the United States Department of Defense (DoD) of
27 the linked websites, of the information, products, or services contained
28 therein. The DoD does not exercise any editorial, security, or other
29 control over the information you may find at these locations.
30*/
31#pragma once
32
33#include <disciplines.grpc.pb.h>
34#include "discipline_server.h"
35
36#include <discipline.h>
37#include "discipline_client.h"
38
39namespace philote
40{
41 // forward declaration
42 class ImplicitDiscipline;
43
53 class ImplicitServer : public ImplicitService::Service
54 {
55 public:
57 ImplicitServer() = default;
58
60 ~ImplicitServer() noexcept;
61
68 void LinkPointers(std::shared_ptr<philote::ImplicitDiscipline> implementation);
69
75
83 grpc::Status ComputeResiduals(grpc::ServerContext *context,
84 grpc::ServerReaderWriter<::philote::Array,
85 ::philote::Array> *stream);
86
94 grpc::Status SolveResiduals(grpc::ServerContext *context,
95 grpc::ServerReaderWriter<::philote::Array,
96 ::philote::Array> *stream);
97
105 grpc::Status ComputeResidualGradients(grpc::ServerContext *context,
106 grpc::ServerReaderWriter<::philote::Array,
107 ::philote::Array> *stream);
108
109 // Test helper methods that accept interface pointers for unit testing with mocks
110 template<typename StreamType>
111 grpc::Status ComputeResidualsImpl(grpc::ServerContext *context, StreamType *stream);
112
113 template<typename StreamType>
114 grpc::Status SolveResidualsImpl(grpc::ServerContext *context, StreamType *stream);
115
116 template<typename StreamType>
117 grpc::Status ComputeResidualGradientsImpl(grpc::ServerContext *context, StreamType *stream);
118
119 // Public wrappers for tests
120 grpc::Status ComputeResidualsForTesting(grpc::ServerContext *context,
121 grpc::ServerReaderWriterInterface<::philote::Array,
122 ::philote::Array> *stream) {
123 return ComputeResidualsImpl(context, stream);
124 }
125
126 grpc::Status SolveResidualsForTesting(grpc::ServerContext *context,
127 grpc::ServerReaderWriterInterface<::philote::Array,
128 ::philote::Array> *stream) {
129 return SolveResidualsImpl(context, stream);
130 }
131
132 grpc::Status ComputeResidualGradientsForTesting(grpc::ServerContext *context,
133 grpc::ServerReaderWriterInterface<::philote::Array,
134 ::philote::Array> *stream) {
135 return ComputeResidualGradientsImpl(context, stream);
136 }
137
138 private:
140 std::shared_ptr<philote::ImplicitDiscipline> implementation_;
141 };
142
291 {
292 public:
298
304
310 void RegisterServices(grpc::ServerBuilder &builder);
311
318 void DeclarePartials(const std::string &f, const std::string &x);
319
330 virtual void ComputeResiduals(const philote::Variables &inputs,
331 const philote::Variables &outputs,
332 philote::Variables &residuals);
333
344 virtual void SolveResiduals(const philote::Variables &inputs, philote::Variables &outputs);
345
357 virtual void ComputeResidualGradients(const philote::Variables &inputs,
358 const philote::Variables &outputs,
359 Partials &partials);
360
361 private:
363 philote::ImplicitServer implicit_;
365 philote::DisciplineServer discipline_server_;
366 };
367
506 {
507 public:
509 ImplicitClient() = default;
510
512 ~ImplicitClient() noexcept = default;
513
519 void ConnectChannel(std::shared_ptr<grpc::ChannelInterface> channel);
520
531
542
550
556 void SetStub(std::unique_ptr<ImplicitService::StubInterface> stub)
557 {
558 stub_ = std::move(stub);
559 }
560
561 private:
563 std::unique_ptr<ImplicitService::StubInterface> stub_;
564 };
565
566// Template implementations must be in header
567#include <algorithm>
568#include <unordered_map>
569
570template<typename StreamType>
571grpc::Status philote::ImplicitServer::ComputeResidualsImpl(grpc::ServerContext *context, StreamType *stream)
572{
573 if (!implementation_)
574 {
575 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "Discipline implementation not linked");
576 }
577
578 if (!stream)
579 {
580 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Stream is null");
581 }
582
583 // Check for cancellation before starting
584 if (context && context->IsCancelled())
585 {
586 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before start");
587 }
588
589 philote::Array array;
590
591 // preallocate the variables based on meta data
592 Variables inputs, outputs, residuals;
593 const auto *discipline = static_cast<philote::Discipline *>(implementation_.get());
594 if (!discipline)
595 {
596 return grpc::Status(grpc::StatusCode::INTERNAL, "Failed to cast implementation to Discipline");
597 }
598
599 for (const auto &var : discipline->var_meta())
600 {
601 std::string name = var.name();
602 if (var.type() == kInput)
603 inputs[name] = Variable(var);
604 if (var.type() == kOutput)
605 {
606 outputs[var.name()] = Variable(var);
607 residuals[var.name()] = Variable(var);
608 }
609 }
610
611 // Build O(1) lookup map for variable metadata
612 std::unordered_map<std::string, const VariableMetaData*> var_lookup;
613 for (const auto &var : discipline->var_meta())
614 {
615 var_lookup[var.name()] = &var;
616 }
617
618 while (stream->Read(&array))
619 {
620 // get variables from the stream message
621 const std::string &name = array.name();
622
623 // get the variable corresponding to the current message using O(1) lookup
624 auto var_it = var_lookup.find(name);
625 if (var_it == var_lookup.end())
626 {
627 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Variable not found: " + name);
628 }
629 const VariableMetaData* var = var_it->second;
630
631 // Validate that the message type matches the metadata type
632 if (array.type() != var->type())
633 {
634 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
635 "Type mismatch for variable " + name + ": expected " +
636 std::to_string(var->type()) + " but received " + std::to_string(array.type()));
637 }
638
639 // obtain the inputs and outputs from the stream
640 if (var->type() == VariableType::kInput)
641 {
642 try
643 {
644 inputs[name].AssignChunk(array);
645 }
646 catch (const std::exception &e)
647 {
648 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
649 "Failed to assign chunk for input " + name + ": " + e.what());
650 }
651 }
652 else if (var->type() == VariableType::kOutput)
653 {
654 try
655 {
656 outputs[name].AssignChunk(array);
657 }
658 catch (const std::exception &e)
659 {
660 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
661 "Failed to assign chunk for output " + name + ": " + e.what());
662 }
663 }
664 else
665 {
666 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
667 "Invalid variable type received for variable: " + name);
668 }
669 }
670
671 // Check for cancellation before expensive computation
672 if (context && context->IsCancelled())
673 {
674 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before computation");
675 }
676
677 // Set context for discipline to check cancellation during compute
678 discipline->SetContext(context);
679
680 // call the discipline developer-defined Compute function
681 try
682 {
683 implementation_->ComputeResiduals(inputs, outputs, residuals);
684 }
685 catch (const std::exception &e)
686 {
687 discipline->ClearContext();
688 return grpc::Status(grpc::StatusCode::INTERNAL,
689 "Failed to compute residuals: " + std::string(e.what()));
690 }
691
692 // Clear context after computation
693 discipline->ClearContext();
694
695 // Check for cancellation before sending results
696 if (context && context->IsCancelled())
697 {
698 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before sending results");
699 }
700
701 // iterate through residuals
702 for (const auto &res : residuals)
703 {
704 const std::string &name = res.first;
705 try
706 {
707 res.second.Send(name, "", stream, discipline->stream_opts().num_double(), context);
708 }
709 catch (const std::exception &e)
710 {
711 return grpc::Status(grpc::StatusCode::INTERNAL,
712 "Failed to send residual " + name + ": " + e.what());
713 }
714 }
715
716 return grpc::Status::OK;
717}
718
719template<typename StreamType>
720grpc::Status philote::ImplicitServer::SolveResidualsImpl(grpc::ServerContext *context, StreamType *stream)
721{
722 if (!implementation_)
723 {
724 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "Discipline implementation not linked");
725 }
726
727 if (!stream)
728 {
729 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Stream is null");
730 }
731
732 // Check for cancellation before starting
733 if (context && context->IsCancelled())
734 {
735 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before start");
736 }
737
738 philote::Array array;
739
740 // preallocate the inputs based on meta data
741 Variables inputs;
742 const auto *discipline = static_cast<philote::Discipline *>(implementation_.get());
743 if (!discipline)
744 {
745 return grpc::Status(grpc::StatusCode::INTERNAL, "Failed to cast implementation to Discipline");
746 }
747
748 for (const auto &var : discipline->var_meta())
749 {
750 std::string name = var.name();
751 if (var.type() == kInput)
752 inputs[name] = Variable(var);
753 }
754
755 // Build O(1) lookup map for variable metadata
756 std::unordered_map<std::string, const VariableMetaData*> var_lookup;
757 for (const auto &var : discipline->var_meta())
758 {
759 var_lookup[var.name()] = &var;
760 }
761
762 while (stream->Read(&array))
763 {
764 // get variables from the stream message
765 const std::string &name = array.name();
766
767 // get the variable corresponding to the current message using O(1) lookup
768 auto var_it = var_lookup.find(name);
769 if (var_it == var_lookup.end())
770 {
771 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Variable not found: " + name);
772 }
773 const VariableMetaData* var = var_it->second;
774
775 // obtain the inputs from the stream (only inputs expected for solve)
776 if (var->type() == VariableType::kInput)
777 {
778 try
779 {
780 inputs[name].AssignChunk(array);
781 }
782 catch (const std::exception &e)
783 {
784 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
785 "Failed to assign chunk for input " + name + ": " + e.what());
786 }
787 }
788 else
789 {
790 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
791 "Expected input variable but received different type for: " + name);
792 }
793 }
794
795 // preallocate outputs
796 Variables outputs;
797 for (const VariableMetaData &var : discipline->var_meta())
798 {
799 if (var.type() == kOutput)
800 outputs[var.name()] = Variable(var);
801 }
802
803 // Check for cancellation before expensive computation
804 if (context && context->IsCancelled())
805 {
806 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before computation");
807 }
808
809 // Set context for discipline to check cancellation during solve
810 discipline->SetContext(context);
811
812 // call the discipline developer-defined Solve function
813 try
814 {
815 implementation_->SolveResiduals(inputs, outputs);
816 }
817 catch (const std::exception &e)
818 {
819 discipline->ClearContext();
820 return grpc::Status(grpc::StatusCode::INTERNAL,
821 "Failed to solve residuals: " + std::string(e.what()));
822 }
823
824 // Clear context after computation
825 discipline->ClearContext();
826
827 // Check for cancellation before sending results
828 if (context && context->IsCancelled())
829 {
830 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before sending results");
831 }
832
833 // iterate through continuous outputs
834 for (const auto &var : outputs)
835 {
836 const std::string &name = var.first;
837 try
838 {
839 var.second.Send(name, "", stream, discipline->stream_opts().num_double(), context);
840 }
841 catch (const std::exception &e)
842 {
843 return grpc::Status(grpc::StatusCode::INTERNAL,
844 "Failed to send output " + name + ": " + e.what());
845 }
846 }
847
848 return grpc::Status::OK;
849}
850
851template<typename StreamType>
852grpc::Status philote::ImplicitServer::ComputeResidualGradientsImpl(grpc::ServerContext *context, StreamType *stream)
853{
854 if (!implementation_)
855 {
856 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "Discipline implementation not linked");
857 }
858
859 if (!stream)
860 {
861 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Stream is null");
862 }
863
864 // Check for cancellation before starting
865 if (context && context->IsCancelled())
866 {
867 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before start");
868 }
869
870 philote::Array array;
871
872 // preallocate the inputs and outputs based on meta data
873 Variables inputs, outputs;
874 const auto *discipline = static_cast<philote::Discipline *>(implementation_.get());
875 if (!discipline)
876 {
877 return grpc::Status(grpc::StatusCode::INTERNAL, "Failed to cast implementation to Discipline");
878 }
879
880 for (const auto &var : discipline->var_meta())
881 {
882 const std::string &name = var.name();
883 if (var.type() == kInput)
884 inputs[name] = Variable(var);
885 if (var.type() == kOutput)
886 outputs[name] = Variable(var);
887 }
888
889 // Build O(1) lookup map for variable metadata
890 std::unordered_map<std::string, const VariableMetaData*> var_lookup;
891 for (const auto &var : discipline->var_meta())
892 {
893 var_lookup[var.name()] = &var;
894 }
895
896 while (stream->Read(&array))
897 {
898 // get variables from the stream message
899 const std::string &name = array.name();
900
901 // get the variable corresponding to the current message using O(1) lookup
902 auto var_it = var_lookup.find(name);
903 if (var_it == var_lookup.end())
904 {
905 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Variable not found: " + name);
906 }
907 const VariableMetaData* var = var_it->second;
908
909 // Validate that the message type matches the metadata type
910 if (array.type() != var->type())
911 {
912 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
913 "Type mismatch for variable " + name + ": expected " +
914 std::to_string(var->type()) + " but received " + std::to_string(array.type()));
915 }
916
917 // obtain the inputs and outputs from the stream
918 if (var->type() == VariableType::kInput)
919 {
920 try
921 {
922 inputs[name].AssignChunk(array);
923 }
924 catch (const std::exception &e)
925 {
926 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
927 "Failed to assign chunk for input " + name + ": " + e.what());
928 }
929 }
930 else if (var->type() == VariableType::kOutput)
931 {
932 try
933 {
934 outputs[name].AssignChunk(array);
935 }
936 catch (const std::exception &e)
937 {
938 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
939 "Failed to assign chunk for output " + name + ": " + e.what());
940 }
941 }
942 else
943 {
944 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
945 "Invalid variable type received for variable: " + name);
946 }
947 }
948
949 // preallocate partials
950 Partials partials;
951 for (const PartialsMetaData &par : discipline->partials_meta())
952 {
953 std::vector<size_t> shape;
954 for (const int64_t &dim : par.shape())
955 shape.push_back(dim);
956
957 partials[std::make_pair(par.name(), par.subname())] = Variable(kOutput, shape);
958 }
959
960 // Check for cancellation before expensive computation
961 if (context && context->IsCancelled())
962 {
963 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before computation");
964 }
965
966 // Set context for discipline to check cancellation during compute
967 discipline->SetContext(context);
968
969 // call the discipline developer-defined Compute function
970 try
971 {
972 implementation_->ComputeResidualGradients(inputs, outputs, partials);
973 }
974 catch (const std::exception &e)
975 {
976 discipline->ClearContext();
977 return grpc::Status(grpc::StatusCode::INTERNAL,
978 "Failed to compute residual gradients: " + std::string(e.what()));
979 }
980
981 // Clear context after computation
982 discipline->ClearContext();
983
984 // Check for cancellation before sending results
985 if (context && context->IsCancelled())
986 {
987 return grpc::Status(grpc::StatusCode::CANCELLED, "Request cancelled before sending results");
988 }
989
990 // iterate through partials
991 for (const auto &par : partials)
992 {
993 const std::string &name = par.first.first;
994 const std::string &subname = par.first.second;
995 try
996 {
997 par.second.Send(name, subname, stream, discipline->stream_opts().num_double(), context);
998 }
999 catch (const std::exception &e)
1000 {
1001 return grpc::Status(grpc::StatusCode::INTERNAL,
1002 "Failed to send partial " + name + "/" + subname + ": " + e.what());
1003 }
1004 }
1005
1006 return grpc::Status::OK;
1007}} // namespace philote
Client class for interacting with a discipline server.
Definition discipline_client.h:55
Base class for all analysis discipline servers.
Definition discipline_server.h:57
Definition of the discipline base class.
Definition discipline.h:62
Client class for calling a remote implicit discipline.
Definition implicit.h:506
ImplicitClient()=default
Constructor.
~ImplicitClient() noexcept=default
Destructor.
Implicit discipline class.
Definition implicit.h:291
void DeclarePartials(const std::string &f, const std::string &x)
Declare a (set of) partial(s) for the discipline.
~ImplicitDiscipline() noexcept
Destroy the Implicit Discipline object.
virtual void SolveResiduals(const philote::Variables &inputs, philote::Variables &outputs)
Solves the residuals to obtain the outputs for the discipline.
virtual void ComputeResidualGradients(const philote::Variables &inputs, const philote::Variables &outputs, Partials &partials)
Computes the gradients of the residuals evaluation for the discipline.
virtual void ComputeResiduals(const philote::Variables &inputs, const philote::Variables &outputs, philote::Variables &residuals)
Computes the residual for the discipline.
void RegisterServices(grpc::ServerBuilder &builder)
Registers all services with a gRPC channel.
ImplicitDiscipline()
Construct a new Implicit Discipline object.
Implicit server class.
Definition implicit.h:54
grpc::Status SolveResidualsImpl(grpc::ServerContext *context, StreamType *stream)
Definition implicit.h:720
grpc::Status ComputeResidualsImpl(grpc::ServerContext *context, StreamType *stream)
Definition implicit.h:571
grpc::Status ComputeResidualsForTesting(grpc::ServerContext *context, grpc::ServerReaderWriterInterface<::philote::Array, ::philote::Array > *stream)
Definition implicit.h:120
grpc::Status ComputeResidualGradientsForTesting(grpc::ServerContext *context, grpc::ServerReaderWriterInterface<::philote::Array, ::philote::Array > *stream)
Definition implicit.h:132
grpc::Status SolveResiduals(grpc::ServerContext *context, grpc::ServerReaderWriter<::philote::Array, ::philote::Array > *stream)
RPC that computes the residual evaluation.
grpc::Status ComputeResidualGradients(grpc::ServerContext *context, grpc::ServerReaderWriter<::philote::Array, ::philote::Array > *stream)
RPC that computes the residual evaluation.
ImplicitServer()=default
Constructor.
grpc::Status SolveResidualsForTesting(grpc::ServerContext *context, grpc::ServerReaderWriterInterface<::philote::Array, ::philote::Array > *stream)
Definition implicit.h:126
void UnlinkPointers()
Dereferences all pointers.
void LinkPointers(std::shared_ptr< philote::ImplicitDiscipline > implementation)
Links the explicit server to the discipline server and explicit discipline via pointers.
grpc::Status ComputeResiduals(grpc::ServerContext *context, grpc::ServerReaderWriter<::philote::Array, ::philote::Array > *stream)
RPC that computes the residual evaluation.
~ImplicitServer() noexcept
Destructor.
grpc::Status ComputeResidualGradientsImpl(grpc::ServerContext *context, StreamType *stream)
Definition implicit.h:852
A class for storing continuous and discrete variables.
Definition variable.h:85
Definition discipline.h:43
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