diff --git a/src/ISADotnet/QueryModel/Assay.fs b/src/ISADotnet/QueryModel/Assay.fs index 4119dd8f..445b28fd 100644 --- a/src/ISADotnet/QueryModel/Assay.fs +++ b/src/ISADotnet/QueryModel/Assay.fs @@ -52,10 +52,10 @@ type QAssay(FileName : string option,MeasurementType : OntologyAnnotation option static member getFinalOutputs (assay : QAssay) = QProcessSequence.getFinalOutputs assay /// Returns the initial inputs final outputs of the assay, to which no processPoints - static member getRootInputOf (assay : QAssay) (sample : string) = QProcessSequence.getRootInputOf assay sample + static member getRootInputOf (assay : QAssay) (sample : string) = QProcessSequence.getRootInputsOfBy (fun _ -> true) sample assay /// Returns the final outputs of the assay, which point to no further nodes - static member getFinalOutputsOf (assay : QAssay) (sample : string) = QProcessSequence.getFinalOutputsOf assay sample + static member getFinalOutputsOf (assay : QAssay) (sample : string) = QProcessSequence.getFinalOutputsOfBy (fun _ -> true) sample assay static member toString (rwa : QAssay) = JsonSerializer.Serialize(rwa,JsonExtensions.options) diff --git a/src/ISADotnet/QueryModel/ProcessSequence.fs b/src/ISADotnet/QueryModel/ProcessSequence.fs index f377c70b..646122c5 100644 --- a/src/ISADotnet/QueryModel/ProcessSequence.fs +++ b/src/ISADotnet/QueryModel/ProcessSequence.fs @@ -14,6 +14,25 @@ type QProcessSequence (sheets : QSheet list) = member this.Sheets = sheets new (processSequence : Process list) = + let updateNodes (sheets : QSheet list) = + let mapping = + sheets + |> List.collect (fun s -> + s.Rows + |> List.collect (fun r -> [r.Input, r.InputType.Value; r.Output, r.OutputType.Value]) + ) + |> List.groupBy fst + |> List.map (fun (name,vs) -> name, vs |> List.map snd |> IOType.reduce) + |> Map.ofList + let updateRow row = + {row with + InputType = Some mapping.[row.Input] + OutputType = Some mapping.[row.Output] + } + sheets + |> List.map (fun sheet -> + {sheet with Rows = sheet.Rows |> List.map updateRow} + ) let sheets = processSequence |> List.groupBy (fun x -> @@ -26,6 +45,7 @@ type QProcessSequence (sheets : QSheet list) = x.Name.Value.Remove lastUnderScoreIndex ) |> List.map (fun (name,processes) -> QSheet.fromProcesses name processes) + |> updateNodes QProcessSequence(sheets) static member fromAssay (assay : Assay) = @@ -65,6 +85,57 @@ type QProcessSequence (sheets : QSheet list) = interface IEnumerable with member this.GetEnumerator() = (this :> IEnumerable).GetEnumerator() :> IEnumerator + + static member getNodes (ps : #QProcessSequence) = + ps.Protocols + |> List.collect (fun p -> p.Rows |> List.collect (fun r -> [r.Input;r.Output])) + |> List.distinct + + static member getSubTreeOf (node : string) (ps : #QProcessSequence) = + let rec collectForwardNodes nodes = + let newNodes = + ps.Sheets + |> List.collect (fun sheet -> + sheet.Rows + |> List.choose (fun r -> if List.contains r.Input nodes then Some r.Output else None) + ) + |> List.append nodes + |> List.distinct + + if newNodes = nodes then nodes + else collectForwardNodes newNodes + + let collectBackwardNodes nodes = + let newNodes = + ps.Sheets + |> List.collect (fun sheet -> + sheet.Rows + |> List.choose (fun r -> if List.contains r.Output nodes then Some r.Input else None) + ) + |> List.append nodes + |> List.distinct + + if newNodes = nodes then nodes + else collectForwardNodes newNodes + + let forwardNodes = collectForwardNodes [node] + let backwardNodes = collectBackwardNodes [node] + + ps.Sheets + |> List.map (fun sheet -> + {sheet + with Rows = + sheet.Rows + |> List.filter (fun r -> + List.contains r.Input forwardNodes + || (List.contains r.Output backwardNodes) + + ) + + } + ) + |> QProcessSequence + /// Returns the initial inputs final outputs of the assay, to which no processPoints static member getRootInputs (ps : #QProcessSequence) = let inputs = ps.Protocols |> List.collect (fun p -> p.Rows |> List.map (fun r -> r.Input)) @@ -79,29 +150,94 @@ type QProcessSequence (sheets : QSheet list) = outputs |> List.filter (fun i -> inputs.Contains i |> not) - /// Returns the initial inputs final outputs of the assay, to which no processPoints - static member getRootInputOf (ps : #QProcessSequence) (sample : string) = + static member getNodesBy (predicate : QueryModel.IOType -> bool) (ps : #QProcessSequence) = + ps.Protocols + |> List.collect (fun p -> + p.Rows + |> List.collect (fun r -> + [if predicate r.InputType.Value then r.Input; if predicate r.OutputType.Value then r.Output]) + ) + |> List.distinct + + static member getRootInputsBy (predicate : QueryModel.IOType -> bool) (ps : #QProcessSequence) = let mappings = ps.Protocols |> List.collect (fun p -> p.Rows - |> List.map (fun r -> r.Output,r.Input) + |> List.map (fun r -> r.Input, r.Output) |> List.distinct ) |> List.groupBy fst |> List.map (fun (out,ins) -> out, ins |> List.map snd) |> Map.ofList - let rec loop lastState state = - if lastState = state then state + + let typeMappings = + ps.Protocols + |> List.collect (fun p -> + p.Rows + |> List.collect (fun r -> [r.Input, r.InputType; r.Output, r.OutputType]) + ) + |> Map.ofList + + let predicate (entity : string) = + match typeMappings.[entity] with + | Some t -> predicate t + | None -> false + + let rec loop (searchEntities : string list) (foundEntities : string list) = + if searchEntities.IsEmpty then foundEntities |> List.distinct else - let newState = - state - |> List.collect (fun s -> - mappings.TryFind s - |> Option.defaultValue [s] - ) - loop state newState - loop [] [sample] + let targs = searchEntities |> List.filter predicate + let nonTargs = searchEntities |> List.filter (predicate >> not) + let nextSearchEntities = nonTargs |> List.collect (fun en -> Map.tryFind en mappings |> Option.defaultValue []) + loop nextSearchEntities targs + + loop (QProcessSequence.getRootInputs ps) [] + + static member getFinalOutputsBy (predicate : QueryModel.IOType -> bool) (ps : #QProcessSequence) = + let mappings = + ps.Protocols + |> List.collect (fun p -> + p.Rows + |> List.map (fun r -> r.Output, r.Input ) + |> List.distinct + ) + |> List.groupBy fst + |> List.map (fun (out,ins) -> out, ins |> List.map snd) + |> Map.ofList + + let typeMappings = + ps.Protocols + |> List.collect (fun p -> + p.Rows + |> List.collect (fun r -> [r.Input, r.InputType; r.Output, r.OutputType]) + ) + |> Map.ofList + + let predicate (entity : string) = + match typeMappings.[entity] with + | Some t -> predicate t + | None -> false + + let rec loop (searchEntities : string list) (foundEntities : string list) = + if searchEntities.IsEmpty then foundEntities |> List.distinct + else + let targs = searchEntities |> List.filter predicate + let nonTargs = searchEntities |> List.filter (predicate >> not) + let nextSearchEntities = nonTargs |> List.collect (fun en -> Map.tryFind en mappings |> Option.defaultValue []) + loop nextSearchEntities targs + + loop (QProcessSequence.getFinalOutputs ps) [] + + /// Returns the initial inputs final outputs of the assay, to which no processPoints + static member getRootInputsOfBy (predicate : QueryModel.IOType -> bool) (sample : string) (ps : #QProcessSequence) = + QProcessSequence.getSubTreeOf sample ps + |> QProcessSequence.getRootInputsBy predicate + + /// Returns the final outputs of the assay, which point to no further nodes + static member getFinalOutputsOfBy (predicate : QueryModel.IOType -> bool) (sample : string) (ps : #QProcessSequence) = + QProcessSequence.getSubTreeOf sample ps + |> QProcessSequence.getFinalOutputsBy predicate /// Returns the initial inputs final outputs of the assay, to which no processPoints static member getPreviousValuesOf (ps : #QProcessSequence) (sample : string) = @@ -157,29 +293,6 @@ type QProcessSequence (sheets : QSheet list) = loop [] [] [sample] |> ValueCollection - /// Returns the final outputs of the assay, which point to no further nodes - static member getFinalOutputsOf (ps : #QProcessSequence) (sample : string) = - let mappings = - ps.Protocols - |> List.collect (fun p -> - p.Rows - |> List.map (fun r -> r.Input,r.Output) - |> List.distinct - ) - |> List.groupBy fst - |> List.map (fun (inp,outs) -> inp, outs |> List.map snd) - |> Map.ofList - let rec loop lastState state = - if lastState = state then state - else - let newState = - state - |> List.collect (fun s -> - mappings.TryFind s - |> Option.defaultValue [s] - ) - loop state newState - loop [] [sample] member this.Nearest = this.Sheets @@ -191,8 +304,9 @@ type QProcessSequence (sheets : QSheet list) = |> List.collect (fun sheet -> sheet.Rows |> List.collect (fun r -> - r.Input - |> QProcessSequence.getRootInputOf this |> List.distinct + + QProcessSequence.getRootInputsOfBy (fun _ -> true) r.Input this + |> List.distinct |> List.collect (fun inp -> r.Values |> List.map (fun v -> @@ -208,8 +322,9 @@ type QProcessSequence (sheets : QSheet list) = |> List.collect (fun sheet -> sheet.Rows |> List.collect (fun r -> - r.Output - |> QProcessSequence.getFinalOutputsOf this |> List.distinct + + QProcessSequence.getFinalOutputsOfBy (fun _ -> true) r.Output this + |> List.distinct |> List.collect (fun out -> r.Values |> List.map (fun v -> @@ -225,8 +340,8 @@ type QProcessSequence (sheets : QSheet list) = |> List.collect (fun sheet -> sheet.Rows |> List.collect (fun r -> - let outs = r.Output |> QProcessSequence.getFinalOutputsOf this |> List.distinct - let inps = r.Input |> QProcessSequence.getRootInputOf this |> List.distinct + let outs = QProcessSequence.getFinalOutputsOfBy (fun _ -> true) r.Output this |> List.distinct + let inps = QProcessSequence.getRootInputsOfBy (fun _ -> true) r.Input this |> List.distinct outs |> List.collect (fun out -> inps @@ -241,6 +356,84 @@ type QProcessSequence (sheets : QSheet list) = ) |> IOValueCollection + member this.Nodes() = + QProcessSequence.getNodes(this) + + member this.FirstNodes() = + QProcessSequence.getRootInputs(this) + + member this.LastNodes() = + QProcessSequence.getFinalOutputs(this) + + member this.FirstNodesOf(node) = + QProcessSequence.getRootInputsOfBy (fun _ -> true) node this + + member this.LastNodesOf(node) = + QProcessSequence.getFinalOutputsOfBy (fun _ -> true) node this + + member this.Samples() = + QProcessSequence.getNodesBy (fun (io : IOType) -> io.isSample) this + + member this.FirstSamples() = + QProcessSequence.getRootInputsBy (fun (io : IOType) -> io.isSample) this + + member this.LastSamples() = + QProcessSequence.getFinalOutputsBy (fun (io : IOType) -> io.isSample) this + + member this.FirstSamplesOf(node) = + QProcessSequence.getRootInputsOfBy (fun (io : IOType) -> io.isSample) node this + + member this.LastSamplesOf(node) = + QProcessSequence.getFinalOutputsOfBy (fun (io : IOType) -> io.isSample) node this + + member this.Sources() = + QProcessSequence.getNodesBy (fun (io : IOType) -> io.isSource) this + + member this.Data() = + QProcessSequence.getNodesBy (fun (io : IOType) -> io.isData) this + + member this.FirstData() = + QProcessSequence.getRootInputsBy (fun (io : IOType) -> io.isData) this + + member this.LastData() = + QProcessSequence.getFinalOutputsBy (fun (io : IOType) -> io.isData) this + + member this.FirstDataOf(node) = + QProcessSequence.getRootInputsOfBy (fun (io : IOType) -> io.isData) node this + + member this.LastDataOf(node) = + QProcessSequence.getFinalOutputsOfBy (fun (io : IOType) -> io.isData) node this + + member this.RawData() = + QProcessSequence.getNodesBy (fun (io : IOType) -> io.isRawData) this + + member this.FirstRawData() = + QProcessSequence.getRootInputsBy (fun (io : IOType) -> io.isRawData) this + + member this.LastRawData() = + QProcessSequence.getFinalOutputsBy (fun (io : IOType) -> io.isRawData) this + + member this.FirstRawDataOf(node) = + QProcessSequence.getRootInputsOfBy (fun (io : IOType) -> io.isRawData) node this + + member this.LastRawDataOf(node) = + QProcessSequence.getFinalOutputsOfBy (fun (io : IOType) -> io.isRawData) node this + + member this.ProcessedData() = + QProcessSequence.getNodesBy (fun (io : IOType) -> io.isProcessedData) this + + member this.FirstProcessedData() = + QProcessSequence.getRootInputsBy (fun (io : IOType) -> io.isProcessedData) this + + member this.LastProcessedData() = + QProcessSequence.getFinalOutputsBy (fun (io : IOType) -> io.isProcessedData) this + + member this.FirstProcessedDataOf(node) = + QProcessSequence.getRootInputsOfBy (fun (io : IOType) -> io.isProcessedData) node this + + member this.LastProcessedDataOf(node) = + QProcessSequence.getFinalOutputsOfBy (fun (io : IOType) -> io.isProcessedData) node this + member this.ValuesOf(name) = (QProcessSequence.getPreviousValuesOf this name).Values @ (QProcessSequence.getSucceedingValuesOf this name).Values |> ValueCollection @@ -260,6 +453,24 @@ type QProcessSequence (sheets : QSheet list) = member this.SucceedingCharacteristicsOf(name) = this.SucceedingValuesOf(name).Characteristics + member this.ParametersOf(name) = + this.ValuesOf(name).Parameters + + member this.PreviousParametersOf(name) = + this.PreviousValuesOf(name).Parameters + + member this.SucceedingParametersOf(name) = + this.SucceedingValuesOf(name).Parameters + + member this.FactorsOf(name) = + this.ValuesOf(name).Factors + + member this.PreviousFactorsOf(name) = + this.PreviousValuesOf(name).Factors + + member this.SucceedingFactorsOf(name) = + this.SucceedingValuesOf(name).Factors + //static member toString (rwa : QAssay) = JsonSerializer.Serialize(rwa,JsonExtensions.options) //static member toFile (path : string) (rwa:QAssay) = diff --git a/src/ISADotnet/QueryModel/Row.fs b/src/ISADotnet/QueryModel/Row.fs index 3d9dd645..f631aec9 100644 --- a/src/ISADotnet/QueryModel/Row.fs +++ b/src/ISADotnet/QueryModel/Row.fs @@ -63,6 +63,20 @@ type IOType = | ProcessOutput.Data d when d.DataType.Value.IsRawData -> RawData | ProcessOutput.Data d -> Data + static member reduce (ioTypes : IOType list) = + let comparer (iot : IOType) = + match iot with + | Source -> 1 + | Sample -> 2 + | Material -> 3 + | Data -> 4 + | RawData -> 5 + | ProcessedData -> 6 + ioTypes + |> List.reduce (fun a b -> + if comparer a > comparer b then a else b + ) + type QRow = { @@ -105,19 +119,24 @@ type QRow = static member fromProcess (proc : Process) : QRow list = let parameterValues = proc.ParameterValues |> Option.defaultValue [] - (proc.Inputs.Value,proc.Outputs.Value) - ||> List.map2 (fun inp out -> - let characteristics = API.ProcessInput.tryGetCharacteristics inp |> Option.defaultValue [] - let factors = API.ProcessOutput.tryGetFactorValues out |> Option.defaultValue [] - - let inputName = inp.GetName - let outputName = out.GetName - - let inputType = IOType.fromInput inp - let outputType = IOType.fromOutput out + List.zip proc.Inputs.Value proc.Outputs.Value + |> List.groupBy (fun (i,o) -> i.GetName,o.GetName) + |> List.map (fun ((inputName,outputName),ios) -> + + let characteristics = + ios |> List.collect (fst >> API.ProcessInput.tryGetCharacteristics >> (Option.defaultValue [])) + |> List.distinct + let factors = + ios |> List.collect (snd >> API.ProcessOutput.tryGetFactorValues >> (Option.defaultValue [])) + |> List.distinct + + let inputType = ios |> List.map (fst >> IOType.fromInput) |> IOType.reduce + let outputType = ios |> List.map (snd >> IOType.fromOutput) |> IOType.reduce QRow.create(inputName, outputName, inputType, outputType, characteristics, parameterValues, factors) + ) + member this.Item (i : int) = this.Values.[i]