func (t *tracker) CLGNames(CLG systemspec.CLG, networkPayload objectspec.NetworkPayload) error { destinationName := CLG.GetName() sourceIDs := networkPayload.GetSources() errors := make(chan error, len(sourceIDs)) wg := sync.WaitGroup{} for _, s := range sourceIDs { wg.Add(1) go func(s string) { behaviourNameKey := key.NewNetworkKey("behaviour-id:%s:behaviour-name", s) name, err := t.Storage().General().Get(behaviourNameKey) if err != nil { errors <- maskAny(err) } else { // The errors channel is capable of buffering one error for each source // ID. The else clause is necessary to queue only one possible error for // each source ID. So in case the name lookup was successful, we are // able to actually persist the single CLG name connection. behaviourNameKey := key.NewNetworkKey("behaviour-name:%s:o:tracker:behaviour-names", name) err := t.Storage().General().PushToSet(behaviourNameKey, destinationName) if err != nil { errors <- maskAny(err) } } wg.Done() }(string(s)) } wg.Wait() select { case err := <-errors: if err != nil { return maskAny(err) } default: // Nothing do here. No error occurred. All good. } return nil }
func (n *network) Calculate(CLG systemspec.CLG, networkPayload objectspec.NetworkPayload) (objectspec.NetworkPayload, error) { n.Log.WithTags(systemspec.Tags{C: nil, L: "D", O: n, V: 13}, "call Calculate") outputs, err := filterError(reflect.ValueOf(CLG.GetCalculate()).Call(networkPayload.GetCLGInput())) if err != nil { return nil, maskAny(err) } newNetworkPayloadConfig := networkpayload.DefaultConfig() newNetworkPayloadConfig.Args = outputs newNetworkPayloadConfig.Context = networkPayload.GetContext() newNetworkPayloadConfig.Destination = networkPayload.GetDestination() newNetworkPayloadConfig.Sources = networkPayload.GetSources() newNetworkPayload, err := networkpayload.New(newNetworkPayloadConfig) if err != nil { return nil, maskAny(err) } return newNetworkPayload, nil }
func (a *activator) Activate(CLG systemspec.CLG, networkPayload objectspec.NetworkPayload) (objectspec.NetworkPayload, error) { a.Log.WithTags(systemspec.Tags{C: nil, L: "D", O: a, V: 13}, "call Activate") // Fetch the queued network payloads. queue is a string of comma separated // JSON objects representing a specific network payload. behaviourID, ok := networkPayload.GetContext().GetBehaviourID() if !ok { return nil, maskAnyf(invalidBehaviourIDError, "must not be empty") } queueKey := key.NewNetworkKey("activate:queue:behaviour-id:%s:network-payload", behaviourID) s, err := a.Storage().General().Get(queueKey) if err != nil { return nil, maskAny(err) } queue, err := stringToQueue(s) if err != nil { return nil, maskAny(err) } // Merge the given network payload with the queue that we just fetched from // storage. We store the extended queue directly after merging it with the // given network payload to definitely track the received network payload, // even if something goes wrong and we need to return an error on the code // below. In case the current queue exeeds a certain amount of payloads, it is // unlikely that the queue is going to be helpful when growing any further. // Thus we cut the queue at some point beyond the interface capabilities of // the requested CLG. Note that it is possible to have multiple network // payloads sent by the same CLG. That might happen in case a specific CLG // wants to fulfil the interface of the requested CLG on its own, even it is // not able to do so with the output of a single calculation. queue = append(queue, networkPayload) queueBuffer := len(getInputTypes(CLG.GetCalculate())) + 1 if len(queue) > queueBuffer { queue = queue[1:] } err = a.persistQueue(queueKey, queue) if err != nil { return nil, maskAny(err) } // This is the list of lookup functions which is executed seuqentially. lookups := []func(CLG systemspec.CLG, queue []objectspec.NetworkPayload) (objectspec.NetworkPayload, error){ a.GetNetworkPayload, a.New, } // Execute one lookup after another. As soon as we find a network payload, we // return it. var newNetworkPayload objectspec.NetworkPayload for _, lookup := range lookups { newNetworkPayload, err = lookup(CLG, queue) if IsNetworkPayloadNotFound(err) { // There could no network payload be found by this lookup. Go on and try // the next one. continue } else if err != nil { return nil, maskAny(err) } // The current lookup was successful. We do not need to execute any further // lookup, but can go on with the network payload found. break } // Filter all network payloads from the queue that are merged into the new // network payload. var newQueue []objectspec.NetworkPayload for _, s := range newNetworkPayload.GetSources() { for _, np := range queue { // At this point there is only one source given. That is the CLG that // forwarded the current network payload to here. If this is not the case, // we return an error. sources := np.GetSources() if len(sources) != 1 { return nil, maskAnyf(invalidSourcesError, "there must be one source") } if s == sources[0] { // The current network payload is part of the merged network payload. // Thus we do not add it to the new queue. continue } newQueue = append(newQueue, np) } } // Update the modified queue in the underlying storage. err = a.persistQueue(queueKey, newQueue) if err != nil { return nil, maskAny(err) } // The current lookup was able to find a network payload. Thus we simply // return it. return newNetworkPayload, nil }
func (a *activator) New(CLG systemspec.CLG, queue []objectspec.NetworkPayload) (objectspec.NetworkPayload, error) { // Track the input types of the requested CLG as string slice to have // something that is easily comparable and efficient. By convention the first // input argument of each CLG is a context. We remove the first argument here, // because we want to match output interfaces against input interfaces. By // convention output interfaces of CLGs must not have a context as first // return value. Therefore we align the input and output values to make them // comparable. clgTypes := typesToStrings(getInputTypes(CLG.GetCalculate()))[1:] // Prepare the permutation list to find out which combination of payloads // satisfies the requested CLG's interface. newPermutationListConfig := permutation.DefaultListConfig() newPermutationListConfig.MaxGrowth = len(clgTypes) newPermutationListConfig.RawValues = queueToValues(queue) newPermutationList, err := permutation.NewList(newPermutationListConfig) if err != nil { return nil, maskAny(err) } // Permute the permutation list of the queued network payloads until we found // all the matching combinations. var possibleMatches [][]objectspec.NetworkPayload for { // Check if the current combination of network payloads already satisfies // the interface of the requested CLG. This is done in the first place to // also handle the very first combination of the permutation list. In case // there does a combination of network payloads match the interface of the // requested CLG, we capture the found combination and try to find more // combinations in the upcoming loops. permutedValues := newPermutationList.GetPermutedValues() valueTypes := typesToStrings(valuesToTypes(permutedValues)) if equalStrings(clgTypes, valueTypes) { possibleMatches = append(possibleMatches, valuesToQueue(permutedValues)) } // Permute the list of the queued network payloads by one further // permutation step within the current iteration. As soon as the permutation // list cannot be permuted anymore, we stop the permutation loop to choose // one random combination of the tracked list in the next step below. err = a.Service().Permutation().PermuteBy(newPermutationList, 1) if permutation.IsMaxGrowthReached(err) { break } else if err != nil { return nil, maskAny(err) } } // We fetched all possible combinations if network payloads that match the // interface of the requested CLG. Now we need to select one random // combination to cover all possible combinations across all possible CLG // trees being created over time. This prevents us from choosing always only // the first matching combination, which would lack discoveries of all // potential combinations being created. matchIndex, err := a.Service().Random().CreateMax(len(possibleMatches)) if err != nil { return nil, maskAny(err) } matches := possibleMatches[matchIndex] // The queued network payloads are able to satisfy the interface of the // requested CLG. We merge the matching network payloads together and return // the result after storing the created configuration of the requested CLG. newNetworkPayload, err := mergeNetworkPayloads(matches) if err != nil { return nil, maskAny(err) } // Persists the combination of permuted network payloads as configuration for // the requested CLG. This configuration is stored using references of the // behaviour IDs associated with CLGs that forwarded signals to this requested // CLG. Note that the order of behaviour IDs must be preserved, because it // represents the input interface of the requested CLG. behaviourID, ok := newNetworkPayload.GetContext().GetBehaviourID() if !ok { return nil, maskAnyf(invalidBehaviourIDError, "must not be empty") } behaviourIDsKey := key.NewNetworkKey("activate:configuration:behaviour-id:%s:behaviour-ids", behaviourID) var behaviourIDs []string for _, behaviourID := range newNetworkPayload.GetSources() { behaviourIDs = append(behaviourIDs, string(behaviourID)) } err = a.Storage().General().Set(behaviourIDsKey, strings.Join(behaviourIDs, ",")) if err != nil { return nil, maskAny(err) } return newNetworkPayload, nil }
func (n *network) InputHandler(CLG systemspec.CLG, textInput objectspec.TextInput) error { // In case the text request defines the echo flag, we overwrite the given CLG // directly to the output CLG. This will cause the created network payload to // be forwarded to the output CLG without indirection. Note that this should // only be used for testing purposes to bypass more complex neural network // activities to directly respond with the received input. if textInput.GetEcho() { var ok bool CLG, ok = n.CLGs["output"] if !ok { return maskAnyf(clgNotFoundError, "name: %s", "output") } } // Create new IDs for the new CLG tree and the input CLG. clgTreeID, err := n.Service().ID().New() if err != nil { return maskAny(err) } behaviourID, err := n.Service().ID().New() if err != nil { return maskAny(err) } // Create a new context and adapt it using the information of the current scope. ctx := context.MustNew() ctx.SetBehaviourID(string(behaviourID)) ctx.SetCLGName(CLG.GetName()) ctx.SetCLGTreeID(string(clgTreeID)) ctx.SetExpectation(textInput.GetExpectation()) ctx.SetSessionID(textInput.GetSessionID()) // We transform the received text request to a network payload to have a // conventional data structure within the neural network. newNetworkPayloadConfig := networkpayload.DefaultConfig() newNetworkPayloadConfig.Args = []reflect.Value{reflect.ValueOf(textInput.GetInput())} newNetworkPayloadConfig.Context = ctx newNetworkPayloadConfig.Destination = behaviourID newNetworkPayloadConfig.Sources = []string{n.GetID()} newNetworkPayload, err := networkpayload.New(newNetworkPayloadConfig) if err != nil { return maskAny(err) } // Write the new CLG tree ID to reference the input CLG ID and add the CLG // tree ID to the new context. firstBehaviourIDKey := key.NewNetworkKey("clg-tree-id:%s:first-behaviour-id", clgTreeID) err = n.Storage().General().Set(firstBehaviourIDKey, string(behaviourID)) if err != nil { return maskAny(err) } // Write the transformed network payload to the queue. eventKey := key.NewNetworkKey("event:network-payload") b, err := json.Marshal(newNetworkPayload) if err != nil { return maskAny(err) } err = n.Storage().General().PushToList(eventKey, string(b)) if err != nil { return maskAny(err) } return nil }