Skip to content
Open
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
3 changes: 3 additions & 0 deletions buildSrc/src/main/groovy/library.java-conventions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ configurations {

dependencies {
tattletale(libs.tattletale)
annotationProcessor(libs.lookup.nb)
configurations.compileClasspath.extendsFrom(configurations.annotationProcessor)
testAnnotationProcessor(libs.lookup.nb)
}

tasks.register('copyDependencies', Copy) {
Expand Down
6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
hec-monolith = "6.0.0"
#dssgui is not used by wrims-engine, but is is being included in the wrims-engine bom and should be kept in sync with hec-monolith for downstream projects.
dssgui = "3.4.18"
lookup = "4.0.0"
lookup = "RELEASE290"
lookup-hec = "4.0.0"
guava = "33.5.0-jre"
commons-io = "2.21.0"
xstream = "1.4.21"
Expand Down Expand Up @@ -40,7 +41,8 @@ lmax = "4.0.0"
hec-monolith = { module = "mil.army.usace.hec:hec-monolith", version.ref = "hec-monolith" }
hec-monolith-bom = { module = "mil.army.usace.hec:hec-monolith-bom", version.ref = "hec-monolith" }
dssgui = { module = "mil.army.usace.hec:dssgui", version.ref = "dssgui" }
lookup-compat = { module = "mil.army.usace.hec:lookup-compat", version.ref = "lookup" }
lookup-compat = { module = "mil.army.usace.hec:lookup-compat", version.ref = "lookup-hec" }
lookup-nb = { module = "org.netbeans.api:org-openide-util-lookup", version.ref = "lookup" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" }
xstream = { module = "com.thoughtworks.xstream:xstream", version.ref = "xstream" }
Expand Down
2 changes: 2 additions & 0 deletions wrims-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ dependencies {
implementation libs.graalvm.python
implementation libs.graalvm.embedding

implementation libs.lookup.nb

// Logging
implementation libs.slf4j
implementation libs.slf4j.log4j.bindings
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gov.ca.water.wrims.engine.core.solver.service;

public interface Solver extends AutoCloseable
{
// The base path to use for the solver lookup
String LOOKUP_PATH = "wrims/solver/";

/**
* run the solver and produce the solution file
*/
void solve();

@FunctionalInterface
interface SolverFactory
{
Solver create(SolverContext context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gov.ca.water.wrims.engine.core.solver.service;


import java.util.ArrayList;
import java.util.List;

import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import gov.ca.water.wrims.engine.core.solver.service.Solver.SolverFactory;

public final class SolverBroker
{
private SolverBroker()
{
throw new UnsupportedOperationException("Cannot instantiate a utility class.");
}

// Look up a single solver by name, providing the context for solver initialization
public static Solver findSolver(String solverName, SolverContext context) throws IllegalArgumentException
{
String lookupPath = Solver.LOOKUP_PATH + solverName;
Lookup lookup = Lookups.forPath(lookupPath);
// We are looking up factories instead of solver implementations because service
// implementations are global singletons, solvers themselves carry state
// It is up to implementations how to load JNI libraries
SolverFactory solver = lookup.lookup(SolverFactory.class);
if(solver == null)
{
throw new IllegalArgumentException("Solver not found: " + solverName);
}
return solver.create(context);
}

// Look up all available solvers
// Retrieves all factories, without instantiating any solver implementations
public static List<SolverFactory> findAllSolvers()
{
Lookup lookup = Lookup.getDefault();
return new ArrayList<>(lookup.lookupAll(SolverFactory.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gov.ca.water.wrims.engine.core.solver.service;

import java.nio.file.Path;

// context provided by model process containing input configuration for the solvers
// The solver context is available for the lifetime of the solver process
public final class SolverContext
{
private Path lpFile;

public Path getLP()
{
return lpFile;
}

// Mutable state should be explicitly scoped to not allow mutation within the solver
void setLP(Path lpFile)
{
this.lpFile = lpFile;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package gov.ca.water.wrims.engine.core.solver.solvers;

import gov.ca.water.wrims.engine.core.solver.service.SolverContext;

// Placeholder abstract class for shared implementation details
public abstract class WrimsSolver
{
private final SolverContext context;

protected WrimsSolver(SolverContext context)
{
this.context = context;
}

public SolverContext getContext()
{
return context;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package gov.ca.water.wrims.engine.core.solver.solvers.cbc;

import java.io.File;

import gov.ca.water.wrims.engine.core.solver.service.Solver;
import gov.ca.water.wrims.engine.core.solver.service.SolverContext;
import gov.ca.water.wrims.engine.core.solver.solvers.WrimsSolver;
import org.apache.commons.io.FilenameUtils;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;

public class CBCSolver extends WrimsSolver implements Solver
{
public static final String SOLVER_TYPE = "CBC";
public static final String SOLVER_TYPE_V1 = "CBC_V1";
private String soluPath;

public CBCSolver(SolverContext context)
{
super(context);
// Solver configuration initialization done using the parameterized context
soluPath = FilenameUtils.removeExtension(context.getLP().toString())+".sn";
File soluF = new File(soluPath);
if (soluF.exists()){
soluF.delete();

Check warning on line 25 in wrims-core/src/main/java/gov/ca/water/wrims/engine/core/solver/solvers/cbc/CBCSolver.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do something with the "boolean" value returned by "delete".

See more on https://sonarcloud.io/project/issues?id=CentralValleyModeling_wrims-engine&issues=AZ30mp_2t-BY4fR05dPd&open=AZ30mp_2t-BY4fR05dPd&pullRequest=225
}
}

@Override
public void solve()
{
// Performing solve operation using CBC
}

@Override
public void close()
{
// Cleaning up resources for CBC solver
}

// Provide both new and old solver paths, allowing this version to be used explicitly
@ServiceProviders(value = {
@ServiceProvider(service = SolverFactory.class, position = 1000),
@ServiceProvider(service = SolverFactory.class, position = 1000, path = Solver.LOOKUP_PATH + SOLVER_TYPE_V1),
@ServiceProvider(service = SolverFactory.class, position = 1000, path = Solver.LOOKUP_PATH + SOLVER_TYPE)
})
public static class CBCSolverFactory implements SolverFactory
{
@Override
public Solver create(SolverContext context)
{
return new CBCSolver(context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package gov.ca.water.wrims.engine.core.solver.solvers.cbc;

import gov.ca.water.wrims.engine.core.solver.service.Solver;
import gov.ca.water.wrims.engine.core.solver.service.SolverContext;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;

public final class CBCSolverV2 extends CBCSolver implements Solver
{
public static final String SOLVER_TYPE_V2 = "CBC_V2";

public CBCSolverV2(SolverContext context)
{
super(context);
}

@Override
public void solve()

Check warning on line 18 in wrims-core/src/main/java/gov/ca/water/wrims/engine/core/solver/solvers/cbc/CBCSolverV2.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this method to simply inherit it.

See more on https://sonarcloud.io/project/issues?id=CentralValleyModeling_wrims-engine&issues=AZ30mp__t-BY4fR05dPe&open=AZ30mp__t-BY4fR05dPe&pullRequest=225
{
// Do something different from CBCSolver V1 here
super.solve();
}

@Override
public void close()
{
// Cleaning up resources for CBC solver version 2
}

// Provide both new and old solver paths, allowing this version to be used explicitly
@ServiceProviders(value = {
@ServiceProvider(service = SolverFactory.class, position = 500),
@ServiceProvider(service = SolverFactory.class, position = 500, path = Solver.LOOKUP_PATH + SOLVER_TYPE, supersedes = {"gov.ca.water.wrims.engine.core.solver.solvers.cbc.CBCSolver"}),
@ServiceProvider(service = SolverFactory.class, position = 500, path = Solver.LOOKUP_PATH + SOLVER_TYPE_V2, supersedes = {"gov.ca.water.wrims.engine.core.solver.solvers.cbc.CBCSolver"}),
})
public static class ImprovedSolverAFactory implements SolverFactory
{
@Override
public Solver create(SolverContext context)
{
return new CBCSolverV2(context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gov.ca.water.wrims.engine.core.solver.solvers.gurobi;

import gov.ca.water.wrims.engine.core.solver.service.Solver;
import gov.ca.water.wrims.engine.core.solver.service.SolverContext;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;

public final class GurobiSolver implements Solver
{
public static final String SOLVER_TYPE = "GUROBI";

public GurobiSolver(SolverContext context)

Check warning on line 12 in wrims-core/src/main/java/gov/ca/water/wrims/engine/core/solver/solvers/gurobi/GurobiSolver.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused method parameter "context".

See more on https://sonarcloud.io/project/issues?id=CentralValleyModeling_wrims-engine&issues=AZ30mp-Dt-BY4fR05dPb&open=AZ30mp-Dt-BY4fR05dPb&pullRequest=225
{
// Initialize solver based on context
}

@Override
public void solve()
{
// Performing solve operation using Gurobi
}

@Override
public void close()
{
// Cleaning up resources for Gurobi solver
}

@ServiceProviders(value = {
@ServiceProvider(service = SolverFactory.class, position = 100),
@ServiceProvider(service = SolverFactory.class, position = 100, path = Solver.LOOKUP_PATH + GurobiSolver.SOLVER_TYPE)
})
public static final class GurobiSolverFactory implements SolverFactory
{
public GurobiSolverFactory()
{
// Given service loading design, this is only executed once
// It is an implementation detail whether to load the library here or in a static initializer block
// The determination of where it is placed should be based on code readability and maintainability

// System.loadLibrary("gurobi_java");

Check warning on line 41 in wrims-core/src/main/java/gov/ca/water/wrims/engine/core/solver/solvers/gurobi/GurobiSolver.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=CentralValleyModeling_wrims-engine&issues=AZ30mp-Dt-BY4fR05dPc&open=AZ30mp-Dt-BY4fR05dPc&pullRequest=225
}

@Override
public Solver create(SolverContext context)
{
return new GurobiSolver(context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package gov.ca.water.wrims.engine.core.solver.service;

import java.nio.file.Path;
import java.util.List;
import gov.ca.water.wrims.engine.core.solver.service.Solver.SolverFactory;

final class SolverLookupExamples
{
void useCBCSolver() throws Exception
{
SolverContext context = new SolverContext();
// Providing all context needed for solvers using LP file as an example
context.setLP(Path.of("test.solve"));
// This will get the newest version of the CBC solver, given the supersedes configuration for CBC solver version 2
try(Solver cbc = SolverBroker.findSolver("CBC", context))
{
cbc.solve();
// Given the solver is in the local context, all computation state is locally scoped,
// which makes this threadsafe and deterministic.
}
// CBC solver is auto-closable, so resources will be closed when out of scope
}

void useOlderVersionOfSolver() throws Exception
{
SolverContext context = new SolverContext();
context.setLP(Path.of("test.solve"));
// This will get the version 1 of the CBC solver, given the multiple service provider definitions
try(Solver cbc = SolverBroker.findSolver("CBC_V1", context))
{
cbc.solve();
}
}

void findNonExistentSolver() throws Exception
{
SolverContext context = new SolverContext();
context.setLP(Path.of("test.solve"));
// This throws an exception, given this LP does not exist and thus is a configuration issue
try(Solver cbc = SolverBroker.findSolver("MACHINE_LEARNING_LP", context))
{
cbc.solve();
}
}

void resolveSolverFactoryOrderBasedOnServicePosition()
{
List<SolverFactory> solvers = SolverBroker.findAllSolvers();

// This is Gurobi because it is the lowest position
SolverFactory gurobi = solvers.getFirst();

Check warning on line 51 in wrims-core/src/test/java/gov/ca/water/wrims/engine/core/solver/service/SolverLookupExamples.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused "gurobi" local variable.

See more on https://sonarcloud.io/project/issues?id=CentralValleyModeling_wrims-engine&issues=AZ30mqAKt-BY4fR05dPf&open=AZ30mqAKt-BY4fR05dPf&pullRequest=225

// This is CBC_V2 because the position is greater than Gurobi, but explicitly supersedes V1
SolverFactory cbcV2 = solvers.get(1);

Check warning on line 54 in wrims-core/src/test/java/gov/ca/water/wrims/engine/core/solver/service/SolverLookupExamples.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused "cbcV2" local variable.

See more on https://sonarcloud.io/project/issues?id=CentralValleyModeling_wrims-engine&issues=AZ30mqAKt-BY4fR05dPg&open=AZ30mqAKt-BY4fR05dPg&pullRequest=225

// This is CBC_V1, as it has the highest position and is explicitly superseded by V2
SolverFactory cbcV1 = solvers.get(2);

Check warning on line 57 in wrims-core/src/test/java/gov/ca/water/wrims/engine/core/solver/service/SolverLookupExamples.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused "cbcV1" local variable.

See more on https://sonarcloud.io/project/issues?id=CentralValleyModeling_wrims-engine&issues=AZ30mqAKt-BY4fR05dPh&open=AZ30mqAKt-BY4fR05dPh&pullRequest=225
}
}
Loading