Skip to content

Commit

Permalink
Add support for log() arithmetic functional operator.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 696932173
  • Loading branch information
evan-gordon authored and copybara-github committed Nov 15, 2024
1 parent d231c68 commit 7015a55
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 1 deletion.
28 changes: 28 additions & 0 deletions interpreter/operator_arithmetic.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,34 @@ func evalLn(_ model.IUnaryExpression, obj result.Value) (result.Value, error) {
return result.New(math.Log(val))
}

// Log(argument Decimal) Decimal
// https://cql.hl7.org/09-b-cqlreference.html#log
func evalLog(_ model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) {
if result.IsNull(lObj) || result.IsNull(rObj) {
return result.New(nil)
}
x, base, err := applyToValues(lObj, rObj, result.ToFloat64)
if err != nil {
return result.Value{}, err
}
val, err := log(x, base)
if err != nil {
return result.New(nil)
}
return result.New(val)
}

// log returns the logarithm of val with given base.
func log(val, base float64) (float64, error) {
if val <= 0 || base <= 0 {
return 0.0, fmt.Errorf("internal error - log base %v for val %v, all values must be greater than 0", base, val)
}
if base == 1 {
return 0.0, fmt.Errorf("internal error - log base %v is undefined", base)
}
return math.Log(val) / math.Log(base), nil
}

// op(left Integer, right Integer) Integer
// https://cql.hl7.org/09-b-cqlreference.html#add
// https://cql.hl7.org/09-b-cqlreference.html#subtract
Expand Down
7 changes: 7 additions & 0 deletions interpreter/operator_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,13 @@ func (i *interpreter) binaryOverloads(m model.IBinaryExpression) ([]convert.Over
Result: evalArithmeticQuantity,
},
}, nil
case *model.Log:
return []convert.Overload[evalBinarySignature]{
{
Operands: []types.IType{types.Decimal, types.Decimal},
Result: evalLog,
},
}, nil
case *model.And, *model.Or, *model.XOr, *model.Implies:
return []convert.Overload[evalBinarySignature]{
{
Expand Down
6 changes: 6 additions & 0 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,9 @@ type Modulo struct{ *BinaryExpression }
// Power ELM Expression https://cql.hl7.org/04-logicalspecification.html#power
type Power struct{ *BinaryExpression }

// Log ELM Expression https://cql.hl7.org/04-logicalspecification.html#log.
type Log struct{ *BinaryExpression }

// TruncatedDivide ELM Expression https://cql.hl7.org/04-logicalspecification.html#truncateddivide
type TruncatedDivide struct{ *BinaryExpression }

Expand Down Expand Up @@ -1194,6 +1197,9 @@ func (a *Floor) GetName() string { return "Floor" }
// GetName returns the name of the system operator.
func (a *Ln) GetName() string { return "Ln" }

// GetName returns the name of the system operator.
func (a *Log) GetName() string { return "Log" }

// GetName returns the name of the system operator.
func (a *Precision) GetName() string { return "Precision" }

Expand Down
11 changes: 11 additions & 0 deletions parser/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,17 @@ func (p *Parser) loadSystemOperators() error {
}
},
},
{
name: "Log",
operands: [][]types.IType{{types.Decimal, types.Decimal}},
model: func() model.IExpression {
return &model.Log{
BinaryExpression: &model.BinaryExpression{
Expression: model.ResultType(types.Decimal),
},
}
},
},
{
name: "Negate",
operands: [][]types.IType{{types.Integer}},
Expand Down
13 changes: 13 additions & 0 deletions parser/operators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,19 @@ func TestBuiltInFunctions(t *testing.T) {
},
},
},
{
name: "Arithmetic Log",
cql: "Log(1.0, 10.0)",
want: &model.Log{
BinaryExpression: &model.BinaryExpression{
Operands: []model.IExpression{
model.NewLiteral("1.0", types.Decimal),
model.NewLiteral("10.0", types.Decimal),
},
Expression: model.ResultType(types.Decimal),
},
},
},
{
name: "Arithmetic Precision",
cql: "Precision(@2014)",
Expand Down
96 changes: 96 additions & 0 deletions tests/enginetests/operator_arithmetic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,102 @@ func TestLn(t *testing.T) {
}
}

func TestLog(t *testing.T) {
tests := []struct {
name string
cql string
wantModel model.IExpression
wantResult result.Value
}{
{
name: "Decimal",
cql: "Log(1.0, 10.0)",
wantModel: &model.Log{
BinaryExpression: &model.BinaryExpression{
Operands: []model.IExpression{
model.NewLiteral("1.0", types.Decimal),
model.NewLiteral("10.0", types.Decimal),
},
Expression: model.ResultType(types.Decimal),
},
},
wantResult: newOrFatal(t, 0.0),
},
{
name: "Negative value",
cql: "Log(-2.1, 10.0)",
wantResult: newOrFatal(t, nil),
},
{
name: "Negative base",
cql: "Log(2.1, -10.0)",
wantResult: newOrFatal(t, nil),
},
{
name: "Zero value",
cql: "Log(0.0, 10.0)",
wantResult: newOrFatal(t, nil),
},
{
name: "Zero base",
cql: "Log(2.1, 0.0)",
wantResult: newOrFatal(t, nil),
},
{
name: "Logarithm of 0.125 with base 2",
cql: "Log(0.125, 2.0)",
wantResult: newOrFatal(t, -3.0),
},
{
name: "Integer of 16 with base 2",
cql: "Log(16, 2)",
wantResult: newOrFatal(t, 4.0),
},
{
name: "Minimum Integer value",
cql: "Log(-2147483648, 10)",
wantResult: newOrFatal(t, nil),
},
{
name: "Maximum Integer value",
cql: "Round(Log(2147483647, 10), 3)",
wantResult: newOrFatal(t, 9.332),
},
{
name: "Null value",
cql: "Log(null as Decimal, 10)",
wantResult: newOrFatal(t, nil),
},
{
name: "Null base",
cql: "Log(1.0, null as Decimal)",
wantResult: newOrFatal(t, nil),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
p := newFHIRParser(t)
parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{})
if err != nil {
t.Fatalf("Parse returned unexpected error: %v", err)
}
if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" {
t.Errorf("Parse diff (-want +got):\n%s", diff)
}

results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p))
if err != nil {
t.Fatalf("Eval returned unexpected error: %v", err)
}

if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" {
t.Errorf("Eval diff (-want +got)\n%v", diff)
}
})
}
}

func TestPrecision(t *testing.T) {
tests := []struct {
name string
Expand Down
1 change: 0 additions & 1 deletion tests/spectests/exclusions/exclusions.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
GroupExcludes: []string{
// TODO: b/342061715 - unsupported operators.
"HighBoundary",
"Log",
"LowBoundary",
},
NamesExcludes: []string{
Expand Down

0 comments on commit 7015a55

Please sign in to comment.