Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ See our [book](https://lca-as-code.com/book) to learn more about the language.

From the source
```bash
git checkout v1.7.12
git checkout v1.8.0
./gradlew :cli:installDist
alias lcaac=$GIT_ROOT/cli/build/install/lcaac/bin/lcaac
lcaac version
Expand Down
2 changes: 1 addition & 1 deletion cli/src/main/resources/META-INF/lcaac.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Thu Aug 14 10:41:08 CEST 2025
author=Kleis Technology
description=LCA as Code CLI
version=1.7.13
version=1.8.0
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@ package ch.kleis.lcaac.core.lang.evaluator

import ch.kleis.lcaac.core.datasource.DataSourceOperations
import ch.kleis.lcaac.core.lang.SymbolTable
import ch.kleis.lcaac.core.lang.evaluator.protocol.CachedOracle
import ch.kleis.lcaac.core.lang.evaluator.protocol.Learner
import ch.kleis.lcaac.core.lang.evaluator.protocol.Oracle
import ch.kleis.lcaac.core.lang.expression.*
import ch.kleis.lcaac.core.lang.register.ProcessKey
import ch.kleis.lcaac.core.math.Operations
import ch.kleis.lcaac.core.math.QuantityOperations
import com.mayakapps.kache.InMemoryKache
import com.mayakapps.kache.KacheStrategy
import com.mayakapps.kache.ObjectKache
import org.slf4j.LoggerFactory

class Evaluator<Q, M>(
class Evaluator<Q, M> private constructor(
private val symbolTable: SymbolTable<Q>,
private val ops: Operations<Q, M>,
private val sourceOps: DataSourceOperations<Q>,
private val cache: ObjectKache<Pair<EProcessTemplate<Q>, EProductSpec<Q>>, EProcess<Q>>
) {
@Suppress("PrivatePropertyName")
private val LOG = LoggerFactory.getLogger(Evaluator::class.java)
private val oracle = CachedOracle(symbolTable, ops, sourceOps)
private val oracle = Oracle(symbolTable, ops, sourceOps, cache)

constructor(symbolTable: SymbolTable<Q>, ops: Operations<Q, M>, sourceOps: DataSourceOperations<Q>) : this(
symbolTable,
ops,
sourceOps,
InMemoryKache(maxSize = 1024) { strategy = KacheStrategy.LRU }
)

fun trace(initialRequests: Set<EProductSpec<Q>>): EvaluationTrace<Q> {
val learner = Learner(initialRequests, ops)
Expand All @@ -44,7 +54,7 @@ class Evaluator<Q, M>(
mapOf(processKey to template)
)
)
return Evaluator(st, ops, sourceOps)
return Evaluator(st, ops, sourceOps, cache)
}

fun trace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,34 @@ import ch.kleis.lcaac.core.lang.SymbolTable
import ch.kleis.lcaac.core.lang.evaluator.step.CompleteTerminals
import ch.kleis.lcaac.core.lang.evaluator.step.Reduce
import ch.kleis.lcaac.core.lang.expression.EProcess
import ch.kleis.lcaac.core.lang.expression.EProcessTemplate
import ch.kleis.lcaac.core.lang.expression.EProductSpec
import ch.kleis.lcaac.core.lang.expression.ProcessAnnotation
import ch.kleis.lcaac.core.lang.resolver.BareProcessResolver
import ch.kleis.lcaac.core.lang.resolver.CachedProcessResolver
import ch.kleis.lcaac.core.lang.resolver.ProcessTemplateResolver
import ch.kleis.lcaac.core.lang.resolver.SubstanceCharacterizationResolver
import ch.kleis.lcaac.core.math.Operations
import com.mayakapps.kache.InMemoryKache
import com.mayakapps.kache.KacheStrategy
import com.mayakapps.kache.ObjectKache
import kotlinx.coroutines.runBlocking

interface Oracle<Q, M> {
class Oracle<Q, M>(
private val symbolTable: SymbolTable<Q>,
private val ops: Operations<Q, M>,
private val sourceOps: DataSourceOperations<Q>,
private val cache: ObjectKache<Pair<EProcessTemplate<Q>, EProductSpec<Q>>, EProcess<Q>>
) {
private val reduceDataExpressions = Reduce(symbolTable, ops, sourceOps)
private val completeTerminals = CompleteTerminals(ops)
private val processTemplateResolver = ProcessTemplateResolver(symbolTable)
private val substanceCharacterizationResolver = SubstanceCharacterizationResolver(symbolTable)

fun answer(ports: Set<Request<Q>>): Set<Response<Q>> {
return ports.mapNotNull {
answerRequest(it)
}.toSet()
}
fun answerRequest(request: Request<Q>): Response<Q>?
}

class CachedOracle<Q, M>(
private val inner: Oracle<Q, M>,
private val cache: ObjectKache<Request<Q>, Response<Q>> = InMemoryKache(maxSize = 1024) {
strategy = KacheStrategy.LRU
}
) : Oracle<Q, M> {
constructor(
symbolTable: SymbolTable<Q>,
ops: Operations<Q, M>,
sourceOps: DataSourceOperations<Q>,
cache: ObjectKache<Request<Q>, Response<Q>> = InMemoryKache(maxSize = 1024) {
strategy = KacheStrategy.LRU
}
): this(
inner = BareOracle(symbolTable, ops, sourceOps),
cache = cache,
)

override fun answerRequest(request: Request<Q>): Response<Q>? {
return runBlocking {
cache.getOrPut(request) {
inner.answerRequest(request)
}
}
}
}

class BareOracle<Q, M>(
val symbolTable: SymbolTable<Q>,
val ops: Operations<Q, M>,
val sourceOps: DataSourceOperations<Q>,
): Oracle<Q, M> {
private val reduceDataExpressions = Reduce(symbolTable, ops, sourceOps)
private val completeTerminals = CompleteTerminals(ops)
private val processTemplateResolver = ProcessTemplateResolver(symbolTable)
private val substanceCharacterizationResolver = SubstanceCharacterizationResolver(symbolTable)

override fun answerRequest(request: Request<Q>): Response<Q>? {
fun answerRequest(request: Request<Q>): Response<Q>? {
return when (request) {
is ProductRequest -> answerProductRequest(request)
is SubstanceRequest -> answerSubstanceRequest(request)
Expand All @@ -75,7 +45,7 @@ class BareOracle<Q, M>(
val template = processTemplateResolver.resolve(spec) ?: return null

val processResolver = if (template.annotations.contains(ProcessAnnotation.CACHED)) {
CachedProcessResolver(symbolTable, ops, sourceOps)
CachedProcessResolver(symbolTable, ops, sourceOps, cache)
} else {
BareProcessResolver(symbolTable, ops, sourceOps)
}
Expand All @@ -98,4 +68,4 @@ class BareOracle<Q, M>(
private fun indexOf(productName: String, process: EProcess<Q>): Int {
return process.products.indexOfFirst { it.product.name == productName }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ch.kleis.lcaac.core.datasource.DataSourceOperations
import ch.kleis.lcaac.core.lang.SymbolTable
import ch.kleis.lcaac.core.lang.evaluator.EvaluationTrace
import ch.kleis.lcaac.core.lang.evaluator.Evaluator
import ch.kleis.lcaac.core.lang.evaluator.EvaluatorException
import ch.kleis.lcaac.core.lang.evaluator.step.CompleteTerminals
import ch.kleis.lcaac.core.lang.evaluator.step.Reduce
import ch.kleis.lcaac.core.lang.expression.*
Expand All @@ -15,51 +16,63 @@ import ch.kleis.lcaac.core.lang.value.QuantityValueOperations
import ch.kleis.lcaac.core.lang.value.TechnoExchangeValue
import ch.kleis.lcaac.core.math.Operations
import ch.kleis.lcaac.core.matrix.ImpactFactorMatrix
import com.mayakapps.kache.InMemoryKache
import com.mayakapps.kache.KacheStrategy
import com.mayakapps.kache.ObjectKache
import kotlinx.coroutines.runBlocking

interface ProcessResolver<Q, M> {
fun resolve(template: EProcessTemplate<Q>, spec: EProductSpec<Q>): EProcess<Q>
}

class CachedProcessResolver<Q, M>(
val symbolTable: SymbolTable<Q>,
val ops: Operations<Q, M>,
val sourceOps: DataSourceOperations<Q>,
private val symbolTable: SymbolTable<Q>,
private val ops: Operations<Q, M>,
private val sourceOps: DataSourceOperations<Q>,
private val cache: ObjectKache<Pair<EProcessTemplate<Q>, EProductSpec<Q>>, EProcess<Q>>
) : ProcessResolver<Q, M> {

override fun resolve(template: EProcessTemplate<Q>, spec: EProductSpec<Q>): EProcess<Q> {
val trace = getTrace(template, spec)
val entryPoint = trace.getEntryPoint()
val analysis = AnalysisProgram(trace.getSystemValue(), entryPoint, ops).run()
val inputQuantity = inputQuantityAnalysis(entryPoint.products, analysis.impactFactors)
return runBlocking {
cache.getOrPut(template to spec) {
val trace = getTrace(template, spec)
val entryPoint = trace.getEntryPoint()
val analysis = AnalysisProgram(trace.getSystemValue(), entryPoint, ops).run()
val inputQuantity = inputQuantityAnalysis(entryPoint.products, analysis.impactFactors)

val inputs = analysis.impactFactors.getInputProducts().map {
EMapper.toETechnoExchange(inputQuantity(it), it)
}
val inputs = analysis.impactFactors.getInputProducts().map {
EMapper.toETechnoExchange(inputQuantity(it), it)
}

val biosphere = analysis.impactFactors.getSubstances().map {
EMapper.toEBioExchange(inputQuantity(it), it)
}
val biosphere = analysis.impactFactors.getSubstances().map {
EMapper.toEBioExchange(inputQuantity(it), it)
}

val impacts = analysis.impactFactors.getIndicators().map {
EMapper.toEImpact(inputQuantity(it), it)
}
val impacts = analysis.impactFactors.getIndicators().map {
EMapper.toEImpact(inputQuantity(it), it)
}

return template.body.copy(
products = entryPoint.products.map { EMapper.toETechnoExchange(it)},
inputs = inputs.map { ETechnoBlockEntry(it) },
biosphere = biosphere.map { EBioBlockEntry(it) },
impacts = impacts.map { EImpactBlockEntry(it)}
)
template.body.copy(
products = entryPoint.products.map { EMapper.toETechnoExchange(it) },
inputs = inputs.map { ETechnoBlockEntry(it) },
biosphere = biosphere.map { EBioBlockEntry(it) },
impacts = impacts.map { EImpactBlockEntry(it) }
)
} ?: throw EvaluatorException("Failed to resolve process for spec $spec")
}
}

private fun getTrace(template: EProcessTemplate<Q>, spec: EProductSpec<Q>): EvaluationTrace<Q> {
val arguments = template.params.plus(spec.fromProcess?.arguments ?: emptyMap())
val newAnnotations = template.annotations.filter { it != ProcessAnnotation.CACHED }.toSet()
val newTemplate = template.copy(annotations = newAnnotations)

val newSymbolTable = symbolTable.copy(processTemplates = symbolTable.processTemplates.override(
ProcessKey(newTemplate.body.name, newTemplate.body.labels.mapValues { it.value.value }),
newTemplate
))
val newSymbolTable = symbolTable.copy(
processTemplates = symbolTable.processTemplates.override(
ProcessKey(newTemplate.body.name, newTemplate.body.labels.mapValues { it.value.value }),
newTemplate
)
)
val evaluator = Evaluator(newSymbolTable, ops, sourceOps)

return evaluator.trace(newTemplate, arguments)
Expand All @@ -80,9 +93,9 @@ class CachedProcessResolver<Q, M>(
}

class BareProcessResolver<Q, M>(
val symbolTable: SymbolTable<Q>,
val ops: Operations<Q, M>,
val sourceOps: DataSourceOperations<Q>,
symbolTable: SymbolTable<Q>,
ops: Operations<Q, M>,
sourceOps: DataSourceOperations<Q>,
) : ProcessResolver<Q, M> {
private val reduceDataExpressions = Reduce(symbolTable, ops, sourceOps)
private val completeTerminals = CompleteTerminals(ops)
Expand All @@ -94,4 +107,4 @@ class BareProcessResolver<Q, M>(
.let(reduceDataExpressions::apply)
.let(completeTerminals::apply)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,45 +25,6 @@ import org.junit.jupiter.api.Test
import kotlin.test.assertTrue

class OracleTest {
@Test
fun cachedOracle() {
// given
val template = EProcessTemplate(
body = EProcess(
name = "eProcess",
products = listOf(
ETechnoExchange(QuantityFixture.oneKilogram, ProductFixture.carrot)
),
impacts = listOf(
ImpactBlockFixture.oneClimateChange
),
)
)
val symbolTable = SymbolTable(
processTemplates = ProcessTemplateRegister.from(mapOf(
ProcessKey("eProcess") to template
))
)
val inner = spyk(BareOracle(symbolTable, BasicOperations, mockk()))
val oracle = CachedOracle(inner = inner)
val requests = setOf(
ProductRequest(
address = Address(0, 0),
value = EProductSpec<BasicNumber>(
"carrot",
)
)
)
// when
oracle.answer(requests)
oracle.answer(requests)

// then
verify(exactly = 1) {
inner.answerRequest(any())
}
}

@Nested
inner class AnswerProductRequest {
private val spec = EProductSpec<BasicNumber>("carrot")
Expand All @@ -90,7 +51,7 @@ class OracleTest {
ProcessKey("eProcess") to template
))
)
val oracle = spyk(BareOracle(symbolTable, BasicOperations, mockk()))
val oracle = spyk(Oracle(symbolTable, BasicOperations, mockk(), mockk()))

mockkConstructor(CachedProcessResolver::class)
every {
Expand Down Expand Up @@ -128,7 +89,7 @@ class OracleTest {
ProcessKey("eProcess") to template
))
)
val oracle = spyk(BareOracle(symbolTable, BasicOperations, mockk()))
val oracle = spyk(Oracle(symbolTable, BasicOperations, mockk(), mockk()))

mockkConstructor(BareProcessResolver::class)
every {
Expand Down
Loading