4

I have a package that initiates some periodic tasks via the ScheduledTasks functionality. The tasks in question also require the use of parallel tools and the launching of external commands. Everything works if I load the package interactively either from Mathematica or MathKernel. However, I need a way to script this procedure, so that Mathematica is started and the package is loaded automatically.

So far, I have tried loading the core package file into the kernel (i.e. MathKernel <MyPackageSource.m), but the kernel exits as soon as the script file is loaded, whereas I need it to stay open to continue to service the ScheduledTasks. I also tried putting the package into the Autoload directory and starting up MathKernel to load it, but encountered a problem because my package uses Import to import a text file, and apparently the import infrastructure is not yet ready for use at the time my package is autoloaded.

So, my question is two-fold: 1. Is there a way to use Import[..., "Text"] inside an autoloaded package?, and 2. Is there an altogether better way to load a package into a Mathematica session in the context of a batch process?

tavr
  • 61
  • 2
  • See this and linked topics: https://mathematica.stackexchange.com/q/131856/5478 – Kuba May 02 '17 at 19:05
  • I will try the suggestions there in the context of my autoload solution, but what I'm really looking for is a way to do this without using Autoload. Is there a way to start mathematica and load an arbitrary package, from the command line? – tavr May 02 '17 at 19:11
  • I see, ok, let's wait for someone with more experience in this area. – Kuba May 02 '17 at 21:07

2 Answers2

2

I have tried loading the core package file into the kernel, but the kernel exits as soon as the script file is loaded, whereas I need it to stay open to continue to service the ScheduledTasks.

I think the kernel exiting is the problem here. You just need the main "thread" to stay in an idle loop so the background can run.

Here's a script file that starts a scheduled task and then stays looping while the task runs:

Print["Watching for file changes in ", Directory[]]
files = FileNames[];
checkForChangedFiles[] := 
(
    latestfiles = FileNames[];
    newfiles = Complement[latestfiles, files];
    If[newfiles =!= {}, Print["Found new files: ", newfiles]];
    removed = Complement[files, latestfiles];
    If[removed =!= {}, Print["Files removed: ", removed];];
    files = latestfiles;
)

task = RunScheduledTask[checkForChangedFiles[], 2];

While[True,
    Pause[1]
]

Call this file watcher.m, and invoke it from command line:

$ math -script watcher.m

You can test it by adding or removing files from a separate process.

In your case, you need to load a package. You can add that code in the beginning, where I have a function definition and before the RunScheduledTask call.

Joel Klein
  • 5,225
  • 1
  • 31
  • 44
  • I can confirm that the infinite loop keeps the kernel open when you load the package file as a script, and allows the scheduled tasks to be serviced. I was hoping for a more robust approach, but this does work. – tavr May 19 '17 at 20:36
  • What's not robust about it? – Joel Klein May 19 '17 at 23:01
  • Pause is keeping the master kernel open, but at the cost of blocking all further evaluations. This isn't much of a concern in batch mode, but it means that I cannot simply load my package in a batch procedure, but must nest it inside a script that keeps the master kernel spinning. Again, not a big deal, but not ideal either. I think what's needed is an option to keep math executable open indefinitely, or else math should respect pending ScheduledTask's and not exit at end of script. – tavr May 20 '17 at 00:37
  • "at the cost of blocking all further evaluations" -- what do you mean? – Joel Klein Jun 06 '17 at 21:39
0

I made an automatic water misting fan. The temperature monitor software checks temperature at certain interval and decides if it should turn on/off the water misting fan. I think what the temperature monitor software does might meet your needs.

Please note that you have to add JLink.jar to this java project.

/Applications/Mathematica.app/Contents/SystemFiles/Links/JLink/JLink.jar

If you use Linux or Windows, you have to modify the path of MathKernel to create KernelLink.

/Applications/Mathematica.app/Contents/MacOS/MathKernel

Java (JDK 8):

import com.wolfram.jlink.KernelLink;
import com.wolfram.jlink.MathLinkException;
import com.wolfram.jlink.MathLinkFactory;

import java.util.Optional;

public class ScheduledTask {
    public static void main(String[] argv) throws MathLinkException {
        createKernelLink()
            .map(link -> loadPackage(link))
            .map(link -> checkTemperature(link));
    }

    private static Optional<KernelLink> createKernelLink() {
        String[] options = {"-linkmode", "launch", "-linkname",
            "/Applications/Mathematica.app/Contents/MacOS/MathKernel"};
        try {
            KernelLink link = MathLinkFactory.createKernelLink(options);
            return Optional.of(link);
        } catch (MathLinkException e) {
            System.out.println("Fatal error opening link: " + e.getMessage());
            return Optional.empty();
        }
    }

    private static KernelLink loadPackage(KernelLink kernelLink) {
        String packagePath = System.getProperty("user.dir") + "/ac.m";
        String loadPackage = "Get[\"" + packagePath + "\"]";
        evaluate(kernelLink, loadPackage);
        return kernelLink;
    }

    private static void evaluate(KernelLink kernelLink, String loadPackage) {
        try {
            kernelLink.discardAnswer();
            kernelLink.evaluate(loadPackage);
            kernelLink.discardAnswer();
        } catch (MathLinkException e) {
            e.printStackTrace();
        }
    }

    private static Void checkTemperature(KernelLink link) {
        int interval = 2000;
        String expr  = "checkTemperature[]";
        return runScheduledTask(link, interval, expr);
    }

    private static Void runScheduledTask(KernelLink link, int interval, String expr) {
        Runnable task = () -> {
            try {
                link.evaluate(expr);
                link.discardAnswer();
                System.out.print(".");
            } catch (MathLinkException e) {
                e.printStackTrace();
            }
        };

        while(true) {
            try{
                task.run();
                Thread.sleep(interval);
            }
            catch(Exception e){
                link.close();
            }
        }
    }
}

Wolfram:

(* wemo switch https://github.com/iancmcc/ouimeaux *)
(* istats https://github.com/Chris911/iStats *)

cpuTemperature[] := Module[{command, xs, cs, position},
  command = {"/usr/local/bin/istats", "extra"};
  xs = RunProcess[command]["StandardOutput"] // ImportString[#, "CSV"] & // First;
  cs = Map[Select[PrintableASCIIQ], Characters@xs] // Flatten;
  position = FirstPosition[cs, "["] // First;
  Take[cs, position-4] // StringJoin // StringCases[#, x__ ~~ ":" ~~ y__->y]&//First//ToExpression]

temperatureHighQ[highTemperature_] := Module[{temperature},
  temperature = cpuTemperature[];
  If[temperature > highTemperature, (Pause[1];
  cpuTemperature[] > temperature), False]]

temperatureLowQ[lowTemperature_] := Module[{temperature},
  temperature = cpuTemperature[];
  If[temperature < lowTemperature, (Pause[1];
  cpuTemperature[] < temperature), False]]

wemoSwitch[x_] := Module[{command},
  If[x, Print["High"], Print["Low"]];
  command =  {"/usr/local/bin/wemo", "switch", "aircondition",
    If[x, "on", "off"]};
  RunProcess[command]];

checkTemperature[] := Module[{high, low},
  high = 50;
  low  = 46;
  Which[
    temperatureHighQ[high], wemoSwitch[True] ,
    temperatureLowQ[low], wemoSwitch[False]]]

aircondition[] := Module[{task, interval},
  interval = 5;
  task = RunScheduledTask[checkTemperature[], interval];
  While[True, Pause[2]]]

wemoSwitch[False]
webcpu
  • 3,182
  • 12
  • 17