Skip to content

Commit

Permalink
implied properties
Browse files Browse the repository at this point in the history
  • Loading branch information
milesstoetzner committed Nov 5, 2024
1 parent 5aaa065 commit 72ba48c
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 51 deletions.
10 changes: 8 additions & 2 deletions src/enricher/constraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ export class ConstraintEnricher {
}
assert.isDefined(left, 'Left not defined')

// TODO: issue a manaul id

// Sanity check
if (!(element.isRelation() || element.isArtifact()))
if (!(element.isRelation() || element.isArtifact() || element.isProperty()))
throw new Error(`${element.Display} is not issued a manual id`)

this.graph.addConstraint({
Expand Down Expand Up @@ -107,7 +109,11 @@ export class ConstraintEnricher {
...this.graph.artifacts,
]) {
for (const properties of element.propertiesMap.values()) {
this.graph.addConstraint({amo: properties.map(it => it.id)})
// TODO: CRITICAL: REVERT THIS
if (properties.length === 0) continue
this.graph.addConstraint({
implies: [properties[0].container.id, {exo: properties.map(it => it.id)}],
})
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/graph/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default class Graph {

plugins: {technology: TechnologyPlugin[]} = {technology: []}

constructor(serviceTemplate: ServiceTemplate) {
constructor(serviceTemplate: ServiceTemplate, options: {nope: boolean} = {nope: false}) {
this.serviceTemplate = serviceTemplate

/**
Expand Down Expand Up @@ -125,7 +125,7 @@ export default class Graph {
/**
* Populator
*/
new Populator(this).run()
new Populator(this, options).run()
}

getNode(name: string | 'SELF' | 'CONTAINER' | 'SOURCE' | 'TARGET', context: Context = {}): Node {
Expand Down
84 changes: 64 additions & 20 deletions src/graph/populator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import * as utils from '#utils'

export class Populator {
graph: Graph
options: {nope: boolean}

constructor(graph: Graph) {
constructor(graph: Graph, options: {nope: boolean} = {nope: false}) {
this.graph = graph
this.options = options
}

run() {
Expand Down Expand Up @@ -392,6 +394,38 @@ export class Populator {
this.graph.properties.push(property)
}

// TODO: move this into normalizer?
// TODO: dont extend after transpiling bratans
// TODO: NEXT: add undefined default alternative
if (!this.options.nope) {
// Ensure that there is only one default property per property name
element.propertiesMap.forEach(properties => {
const alternative = properties.find(it => it.defaultAlternative)
if (check.isDefined(alternative)) return

const some = properties[0]
assert.isDefined(some)

console.log(`not defined for ${some.display}`)

const property = new Property({
name: some.name,
container: some.container,
index: properties.length,
raw: {
value: 'VINTNER_UNDEFINED',
default_alternative: true,
implied: true,
},
})

property.graph = this.graph
properties.push(property)
element.properties.push(property)
this.graph.properties.push(property)
})
}

// Ensure that there is only one default property per property name
element.propertiesMap.forEach(properties => {
const candidates = properties.filter(it => it.defaultAlternative)
Expand Down Expand Up @@ -481,27 +515,37 @@ export class Populator {
/**
* We only support simple consumers, i.e., directly accessed by properties
*/
// TODO: unfurl syntax support?
private populateConsumers() {
for (const input of this.graph.inputs) {
for (const property of this.graph.properties) {
const value = property.value
if (check.isObject(value) && !check.isArray(value) && check.isDefined(value.get_input)) {
// Referenced by name
if (check.isString(value.get_input)) {
if (value.get_input === input.name) input.consumers.push(property)
continue
}

// Referenced by index
if (check.isNumber(value.get_input)) {
if (value.get_input === input.index) input.consumers.push(property)
continue
}
for (const property of this.graph.properties) {
const value = property.value
if (!check.isObject(value) || check.isArray(value)) continue

throw new Error(
`${property.Display} has neither number nor string in get_input but "${value.get_input}"`
)
}
if (check.isDefined(value.get_input)) {
assert.isStringOrNumber(value.get_input)
const input = this.graph.getInput(value.get_input)

input.consumers.push(property)
property.consuming = input
}

if (check.isDefined(value.get_property)) {
assert.isArray(value.get_property)
assert.isString(value.get_property[0])
assert.isString(value.get_property[1])
// TODO: find all properties? only component?
// TODO: only for nodes?
property.consuming = this.graph.getNodeProperty([value.get_property[0], value.get_property[1]], {
element: property.container,
})
console.log(`${property.Display} is consuming ${property.consuming.display}`)
}

if (check.isDefined(value.get_attribute)) {
assert.isArray(value.get_attribute)
assert.isString(value.get_attribute[0])
// TODO: this is only correct for get_attribute SELF
property.consuming = property.container
}
}
}
Expand Down
25 changes: 23 additions & 2 deletions src/graph/property.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as check from '#check'
import {bratify} from '#graph/utils'
import Input from '#graph/input'
import {andify, bratify} from '#graph/utils'
import {ArtifactDefinition} from '#spec/artifact-definitions'
import {GroupTemplate} from '#spec/group-template'
import {NodeTemplate} from '#spec/node-template'
Expand Down Expand Up @@ -33,6 +34,8 @@ export default class Property extends Element {
value?: PropertyAssignmentValue
readonly expression?: ValueExpression

consuming?: Property | Input | Node | Relation | Group | Policy | Artifact

constructor(data: {
name: string
raw: ConditionalPropertyAssignmentValue
Expand Down Expand Up @@ -91,7 +94,17 @@ export default class Property extends Element {
// TODO: getTypeSpecificCondition, however, get type from type definition being part of the container type ...

getElementGenericCondition() {
return [{conditions: this.container.presenceCondition, consistency: true, semantic: false}]
const conditions: LogicExpression[] = []

// Container pruning
conditions.push(this.container.presenceCondition)

// Input value pruning
if (check.isDefined(this.consuming)) {
conditions.push(this.consuming.presenceCondition)
}

return [{conditions: andify(conditions), consistency: true, semantic: false}]
}

constructPresenceCondition() {
Expand All @@ -103,6 +116,14 @@ export default class Property extends Element {
return bratify(this.container.propertiesMap.get(this.name)!.filter(it => it !== this))
}

/**
* A property is implied by default.
* This is also okay for default alternatives since bratan condition is a manual condition.
*/
get implied() {
return this.raw.implied ?? true
}

isProperty() {
return true
}
Expand Down
39 changes: 20 additions & 19 deletions src/resolver/solver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,28 +254,29 @@ export default class Solver {

// Add manual conditions of a relation separately as own variable into the sat solver.
// Manual conditions are referenced by has_incoming_relation and has_artifact as well as by implied relations.
if (element.isRelation() || element.isArtifact()) {
// Optimization if manual conditions are empty, thus, "true" is fallback
if (utils.isEmpty(conditions)) {
this.minisat.require(element.manualId)
} else {
this.minisat.require(
MiniSat.equiv(
element.manualId,
this.transformLogicExpression(
andify(
conditions.filter(it => {
// This also includes _bratan
if (check.isObject(it)) return !it._generated
return true
})
),
{element}
)
// Also, they are used to prevent pruning input-consuming properties
//if (element.isRelation() || element.isArtifact() || element.isProperty()) {
// Optimization if manual conditions are empty, thus, "true" is fallback
if (utils.isEmpty(conditions)) {
this.minisat.require(element.manualId)
} else {
this.minisat.require(
MiniSat.equiv(
element.manualId,
this.transformLogicExpression(
andify(
conditions.filter(it => {
// This also includes _bratan
if (check.isObject(it)) return !it._generated
return true
})
),
{element}
)
)
}
)
}
//}

// If there are no conditions assigned, then the element is present
if (utils.isEmpty(conditions)) return this.minisat.require(element.id)
Expand Down
1 change: 1 addition & 0 deletions src/specification/property-assignments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type PropertyAssignmentValue =
| boolean
| PropertyAssignmentValue[]
| {[key: string]: PropertyAssignmentValue}
| {get_property: [string, string]; get_input: string; get_attribute: [string, string]}

export type PropertyAssignmentList = PropertyAssignmentListEntry[]
export type PropertyAssignmentListEntry = {
Expand Down
3 changes: 2 additions & 1 deletion tests/minisat/minisat.play.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ async function play(data: string) {

hotfixPersistentCheck(template)

const solver = new Solver(new Graph(template), {})
const solver = new Solver(new Graph(template, {nope: true}), {})

const results = solver.runAll().map(it => utils.sort(it))
std.log(`Results: ${results.length}`)
std.log(results)
Expand Down
2 changes: 1 addition & 1 deletion tests/minisat/plays/enrich.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
set -e

task cli -- template enrich --template ${PWD}/old.minimal.yaml --output ${PWD}/enriched.old.minimal.yaml
./task template enrich --template ${PWD}/old.minimal.yaml --output ${PWD}/enriched.old.minimal.yaml
31 changes: 27 additions & 4 deletions tests/minisat/plays/play.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
tosca_definitions_version: tosca_variability_1_0
tosca_definitions_version: tosca_variability_1_0_rc_3

node_types:
container:
type: tosca.nodes.Root

topology_template:
variability:
options:
type_default_condition: true
input_default_condition: true
property_default_condition: true
enrich_implementations_check: false
required_technology_check: false

inputs:
some_input:
type: string

node_templates:
container:
type: container
conditions: true
properties:
- some_property:
value: {get_input: some_input}
conditions: false
implied: true

- some_property:
value: something else
conditions: false
implied: true

# - some_property:
# value: NOT_DEFINED
# default_alternative: true
# implied: true

0 comments on commit 72ba48c

Please sign in to comment.