diff --git a/docs/content/gosl.md b/docs/content/gosl.md index 3301e822..d80e3955 100644 --- a/docs/content/gosl.md +++ b/docs/content/gosl.md @@ -1,6 +1,10 @@ -**Gosl** allows you to write Go programs that run on [[GPU]] hardware, by transpiling Go into the WGSL shader language used by [WebGPU](https://www.w3.org/TR/webgpu/), thereby establishing the _Go shader language_. ++++ +Name = "GoSL" ++++ -Gosl uses the [core gpu](https://github.com/cogentcore/core/tree/main/gpu) compute shader system, and operates within the overall [[Goal]] framework of an augmented version of the Go language. +**GoSL** allows you to write Go programs that run on [[GPU]] hardware, by transpiling Go into the WGSL shader language used by [WebGPU](https://www.w3.org/TR/webgpu/), thereby establishing the _Go shader language_. + +GoSL uses the [core gpu](https://github.com/cogentcore/core/tree/main/gpu) compute shader system, and operates within the overall [[Goal]] framework of an augmented version of the Go language. The relevant regions of Go code to be run on the GPU are tagged using the `//gosl:start` and `//gosl:end` comment directives, and this code must only use basic expressions and concrete types that will compile correctly in a GPU shader (see [[#Restrictions]] below). Method functions and pass-by-reference pointer arguments to `struct` types are supported and incur no additional compute cost due to inlining (see notes below for more detail). @@ -27,6 +31,8 @@ There are two key elements for GPU-enabled code: 2. [[#Global variables]] on which the kernel functions _exclusively_ operate: all relevant data must be specifically copied from the CPU to the GPU and back. As explained in the [[GPU]] docs, each GPU compute shader is effectively a _standalone_ program operating on these global variables. To replicate this environment on the CPU, so the code works in both contexts, we need to make these variables global in the CPU (Go) environment as well. +**IMPORTANT:** All tensor variables must be sent to the GPU at least once before running, because the generated code does not know the size of the tensor until this is done! This is true even if a variable is just a results variable that does not logically need to be uploaded to the GPU -- the overhead at startup for a single such transfer is not worth the extra complexity of creating the necessary alternative init code. + `gosl` generates a file named `gosl.go` in your package directory that initializes the GPU with all of the global variables, and functions for running the kernels and syncing the gobal variable data back and forth between the CPu and GPU. ## Kernels @@ -35,11 +41,18 @@ Each distinct compute kernel must be tagged with a `//gosl:kernel` comment direc ```go // Compute does the main computation. func Compute(i uint32) { //gosl:kernel + if i >= Params[0].DataLen { // note: essential to bounds check b/c i in 64 blocks + return + } Params[0].IntegFromRaw(int(i)) } ``` -The kernel functions receive a `uint32` index argument, and use this to index into the global variables containing the relevant data. Typically the kernel code itself just calls other relevant function(s) using the index, as in the above example. Critically, _all_ of the data that a kernel function ultimately depends on must be contained with the global variables, and these variables must have been sync'd up to the GPU from the CPU prior to running the kernel (more on this below). +The kernel functions receive a `uint32` index argument, and use this to index into the global variables containing the relevant data. + +**IMPORTANT:** the dispatch of kernels on the GPU is in blocks of 64 processors, so i will exceed the number that you pass into the `Run` function! It is essential to check bounds in every kernel. + +Typically the kernel code itself just calls other relevant function(s) using the index, as in the above example. Critically, _all_ of the data that a kernel function ultimately depends on must be contained with the global variables, and these variables must have been sync'd up to the GPU from the CPU prior to running the kernel (more on this below). In the CPU mode, the kernel is effectively run in a `for` loop like this: ```go @@ -68,7 +81,7 @@ var ( ``` All such variables must be either: -1. A `slice` of GPU-alignment compatible `struct` types, such as `ParamStruct` in the above example. In general such structs should be marked as `//gosl:read-only` due to various challenges associated with writing to structs, detailed below. +1. A `slice` of GPU-alignment compatible `struct` types, such as `ParamStruct` in the above example. In general such structs should be marked as `//gosl:read-only` due to various challenges associated with writing to structs, detailed below. Due to lack of package-relative naming in the final WGSL file, any struct type defined in a sub package will show up unqualified in the generated `gosl.go` file, so a type alias is required to allow the resulting Go file to build properly. 2. A `tensor` of a GPU-compatible elemental data type (`float32`, `uint32`, or `int32`), with the number of dimensions indicated by the `//gosl:dims ` tag as shown above. This is the preferred type for writable data. You can also just declare a slice of elemental GPU-compatible data values such as `float32`, but it is generally preferable to use the tensor instead, because it has built-in support for higher-dimensional indexing in a way that is transparent between CPU and GPU. @@ -232,7 +245,7 @@ var PathGBuf: array>; atomicAdd(&PathGBuf[idx], val); ``` -This also unfortunately has the side-effect that you cannot do _non-atomic_ operations on atomic variables, as discussed extensively here: https://github.com/gpuweb/gpuweb/issues/2377 Gosl automatically detects the use of atomic functions on GPU variables, and tags them as atomic. +This also unfortunately has the side-effect that you cannot do _non-atomic_ operations on atomic variables, as discussed extensively here: https://github.com/gpuweb/gpuweb/issues/2377 GoSL automatically detects the use of atomic functions on GPU variables, and tags them as atomic. ## Random numbers: slrand @@ -244,9 +257,13 @@ See [[doc:gosl/slrand]] for a shader-optimized random number generation package, //gosl:end mycode ``` +## WGSL vector variables from math32.Vector2 etc: not yet + +WGSL supports variables like `vec4` which is equivalent to `math32.Vector4`. Unfortunately, it would be difficult to have simultaneous, transparent support for both of these types across Go and WGSL, requiring rewriting expressions on the WGSL side. It is possible, but would take a fair amount of work, and is not yet planned. + ## Performance With sufficiently large N, and ignoring the data copying setup time, around ~80x speedup is typical on a Macbook Pro with M1 processor. The `rand` example produces a 175x speedup! -## Gosl pages + diff --git a/docs/content/math.md b/docs/content/math.md index d97a917c..21b89bef 100644 --- a/docs/content/math.md +++ b/docs/content/math.md @@ -64,7 +64,7 @@ fmt.Println("d:", d) See [[tensor math#Alignment of shapes]] for more details on [[tensor math]] operations, using the NumPy [broadcasting](https://numpy.org/doc/stable/user/basics.broadcasting.html) logic. -### Tensorfs +### TensorFS In an interactive Goal shell (which we simulate here in the docs), variables in math mode are automatically saved to the [[tensorfs]] virtual data filesystem: @@ -371,7 +371,7 @@ todo: huge amount of work needed to support complex numbers throughout! | . | . |`np.fft.ifft(a)` | inverse Fourier transform of `a` | | . | . |`signal.resample(x, np.ceil(len(x)/q))` | downsample with low-pass filtering | -### Tensorfs +### TensorFS The [[tensorfs]] data filesystem provides a global filesystem-like workspace for storing tensor data, and [[Goal]] has special commands and functions to facilitate interacting with it. diff --git a/docs/content/tensorfs.md b/docs/content/tensorfs.md index 2a42e84a..7cc4c102 100644 --- a/docs/content/tensorfs.md +++ b/docs/content/tensorfs.md @@ -1,8 +1,8 @@ +++ -Categories = ["Tensorfs"] +Name = "TensorFS" +++ -**tensorfs** provides a virtual filesystem for [[tensor]] data, which can be accessed for example in [[Goal]] [[math]] mode expressions, like the variable storage system in [IPython / Jupyter](https://ipython.readthedocs.io/en/stable/interactive/tutorial.html), with the advantage that the hierarchical structure of a filesystem allows data to be organized in more intuitive and effective ways. For example, data at different time scales can be put into different directories, or multiple different statistics computed on a given set of data can be put into a subdirectory. [[stats#Groups]] creates pivot-table style groups of values as directories, for example. +**TensorFS** provides a virtual filesystem for [[tensor]] data, which can be accessed for example in [[Goal]] [[math]] mode expressions, like the variable storage system in [IPython / Jupyter](https://ipython.readthedocs.io/en/stable/interactive/tutorial.html), with the advantage that the hierarchical structure of a filesystem allows data to be organized in more intuitive and effective ways. For example, data at different time scales can be put into different directories, or multiple different statistics computed on a given set of data can be put into a subdirectory. [[stats#Groups]] creates pivot-table style groups of values as directories, for example. `tensorfs` implements the Go [fs](https://pkg.go.dev/io/fs) interface, and can be accessed using fs-general tools, including the cogent core `filetree` and the [[Goal]] shell. diff --git a/docs/docs.go b/docs/docs.go index 9dff8684..ac19b7a3 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -22,6 +22,7 @@ func main() { b := core.NewBody("Cogent Lab") ct := content.NewContent(b).SetContent(econtent) ctx := ct.Context + content.OfflineURL = "https://cogentcore.org/lab" ctx.AddWikilinkHandler(htmlcore.GoDocWikilink("doc", "cogentcore.org/lab")) b.AddTopBar(func(bar *core.Frame) { tb := core.NewToolbar(bar) diff --git a/examples/baremetal/api.go b/examples/baremetal/api.go index a81eee4d..2381fa1a 100644 --- a/examples/baremetal/api.go +++ b/examples/baremetal/api.go @@ -79,3 +79,11 @@ func (bm *BareMetal) UpdateJobs() (nrun, nfinished int, err error) { bm.saveState() return } + +// RecoverJob reinstates job information so files can be recovered etc. +func (bm *BareMetal) RecoverJob(job *Job) (*Job, error) { + bm.Lock() + defer bm.Unlock() + + return bm.recoverJob(job) +} diff --git a/examples/baremetal/baremetal/baremetal.pb.go b/examples/baremetal/baremetal/baremetal.pb.go index 665f1aa3..d221cc04 100644 --- a/examples/baremetal/baremetal/baremetal.pb.go +++ b/examples/baremetal/baremetal/baremetal.pb.go @@ -534,7 +534,7 @@ var file_baremetal_baremetal_proto_rawDesc = []byte{ 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x65, 0x64, 0x10, 0x05, 0x32, 0xa1, 0x02, 0x0a, 0x09, 0x42, 0x61, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x65, 0x64, 0x10, 0x05, 0x32, 0xcf, 0x02, 0x0a, 0x09, 0x42, 0x61, 0x72, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x6c, 0x12, 0x2f, 0x0a, 0x06, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x12, 0x15, 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, @@ -552,11 +552,14 @@ var file_baremetal_baremetal_proto_rawDesc = []byte{ 0x3c, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x31, 0x5a, - 0x2f, 0x63, 0x6f, 0x67, 0x65, 0x6e, 0x74, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x2f, - 0x6c, 0x61, 0x62, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x62, 0x61, 0x72, - 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x2f, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x2c, 0x0a, + 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x2e, 0x62, 0x61, + 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x2e, 0x4a, 0x6f, 0x62, 0x1a, 0x0e, 0x2e, 0x62, 0x61, + 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x2e, 0x4a, 0x6f, 0x62, 0x42, 0x31, 0x5a, 0x2f, 0x63, + 0x6f, 0x67, 0x65, 0x6e, 0x74, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x6c, 0x61, + 0x62, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x62, 0x61, 0x72, 0x65, 0x6d, + 0x65, 0x74, 0x61, 0x6c, 0x2f, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -594,13 +597,15 @@ var file_baremetal_baremetal_proto_depIdxs = []int32{ 4, // 7: baremetal.BareMetal.CancelJobs:input_type -> baremetal.JobIDList 4, // 8: baremetal.BareMetal.FetchResults:input_type -> baremetal.JobIDList 7, // 9: baremetal.BareMetal.UpdateJobs:input_type -> google.protobuf.Empty - 2, // 10: baremetal.BareMetal.Submit:output_type -> baremetal.Job - 3, // 11: baremetal.BareMetal.JobStatus:output_type -> baremetal.JobList - 5, // 12: baremetal.BareMetal.CancelJobs:output_type -> baremetal.Error - 3, // 13: baremetal.BareMetal.FetchResults:output_type -> baremetal.JobList - 7, // 14: baremetal.BareMetal.UpdateJobs:output_type -> google.protobuf.Empty - 10, // [10:15] is the sub-list for method output_type - 5, // [5:10] is the sub-list for method input_type + 2, // 10: baremetal.BareMetal.RecoverJob:input_type -> baremetal.Job + 2, // 11: baremetal.BareMetal.Submit:output_type -> baremetal.Job + 3, // 12: baremetal.BareMetal.JobStatus:output_type -> baremetal.JobList + 5, // 13: baremetal.BareMetal.CancelJobs:output_type -> baremetal.Error + 3, // 14: baremetal.BareMetal.FetchResults:output_type -> baremetal.JobList + 7, // 15: baremetal.BareMetal.UpdateJobs:output_type -> google.protobuf.Empty + 2, // 16: baremetal.BareMetal.RecoverJob:output_type -> baremetal.Job + 11, // [11:17] is the sub-list for method output_type + 5, // [5:11] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name diff --git a/examples/baremetal/baremetal/baremetal.proto b/examples/baremetal/baremetal/baremetal.proto index 3b980e1b..f6f6a03d 100644 --- a/examples/baremetal/baremetal/baremetal.proto +++ b/examples/baremetal/baremetal/baremetal.proto @@ -12,6 +12,7 @@ service BareMetal { rpc CancelJobs (JobIDList) returns (Error); rpc FetchResults (JobIDList) returns (JobList); rpc UpdateJobs (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc RecoverJob (Job) returns (Job); } // Submission is a job submission. diff --git a/examples/baremetal/baremetal/baremetal_grpc.pb.go b/examples/baremetal/baremetal/baremetal_grpc.pb.go index 2765164c..7bc914ed 100644 --- a/examples/baremetal/baremetal/baremetal_grpc.pb.go +++ b/examples/baremetal/baremetal/baremetal_grpc.pb.go @@ -25,6 +25,7 @@ const ( BareMetal_CancelJobs_FullMethodName = "/baremetal.BareMetal/CancelJobs" BareMetal_FetchResults_FullMethodName = "/baremetal.BareMetal/FetchResults" BareMetal_UpdateJobs_FullMethodName = "/baremetal.BareMetal/UpdateJobs" + BareMetal_RecoverJob_FullMethodName = "/baremetal.BareMetal/RecoverJob" ) // BareMetalClient is the client API for BareMetal service. @@ -36,6 +37,7 @@ type BareMetalClient interface { CancelJobs(ctx context.Context, in *JobIDList, opts ...grpc.CallOption) (*Error, error) FetchResults(ctx context.Context, in *JobIDList, opts ...grpc.CallOption) (*JobList, error) UpdateJobs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + RecoverJob(ctx context.Context, in *Job, opts ...grpc.CallOption) (*Job, error) } type bareMetalClient struct { @@ -96,6 +98,16 @@ func (c *bareMetalClient) UpdateJobs(ctx context.Context, in *emptypb.Empty, opt return out, nil } +func (c *bareMetalClient) RecoverJob(ctx context.Context, in *Job, opts ...grpc.CallOption) (*Job, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Job) + err := c.cc.Invoke(ctx, BareMetal_RecoverJob_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // BareMetalServer is the server API for BareMetal service. // All implementations must embed UnimplementedBareMetalServer // for forward compatibility. @@ -105,6 +117,7 @@ type BareMetalServer interface { CancelJobs(context.Context, *JobIDList) (*Error, error) FetchResults(context.Context, *JobIDList) (*JobList, error) UpdateJobs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + RecoverJob(context.Context, *Job) (*Job, error) mustEmbedUnimplementedBareMetalServer() } @@ -130,6 +143,9 @@ func (UnimplementedBareMetalServer) FetchResults(context.Context, *JobIDList) (* func (UnimplementedBareMetalServer) UpdateJobs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateJobs not implemented") } +func (UnimplementedBareMetalServer) RecoverJob(context.Context, *Job) (*Job, error) { + return nil, status.Errorf(codes.Unimplemented, "method RecoverJob not implemented") +} func (UnimplementedBareMetalServer) mustEmbedUnimplementedBareMetalServer() {} func (UnimplementedBareMetalServer) testEmbeddedByValue() {} @@ -241,6 +257,24 @@ func _BareMetal_UpdateJobs_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _BareMetal_RecoverJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Job) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BareMetalServer).RecoverJob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: BareMetal_RecoverJob_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BareMetalServer).RecoverJob(ctx, req.(*Job)) + } + return interceptor(ctx, in, info, handler) +} + // BareMetal_ServiceDesc is the grpc.ServiceDesc for BareMetal service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -268,6 +302,10 @@ var BareMetal_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpdateJobs", Handler: _BareMetal_UpdateJobs_Handler, }, + { + MethodName: "RecoverJob", + Handler: _BareMetal_RecoverJob_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "baremetal/baremetal.proto", diff --git a/examples/baremetal/client.go b/examples/baremetal/client.go index 420da7c5..0eafe941 100644 --- a/examples/baremetal/client.go +++ b/examples/baremetal/client.go @@ -114,3 +114,17 @@ func (cl *Client) FetchResults(resultsGlob string, ids ...int) ([]*Job, error) { func (cl *Client) UpdateJobs() { return } + +// RecoverJob recovers a job which has been lost somehow. +// It just adds the given job to the job table. +func (cl *Client) RecoverJob(job *Job) (*Job, error) { + ctx, cancel := context.WithTimeout(context.Background(), cl.Timeout) + defer cancel() + + pjob := JobToPB(job) + rjob, err := cl.client.RecoverJob(ctx, pjob) + if err != nil { + return nil, errors.Log(fmt.Errorf("RecoverJob failed: %v", err)) + } + return JobFromPB(rjob), nil +} diff --git a/examples/baremetal/cmd/baremetal/main.go b/examples/baremetal/cmd/baremetal/main.go index 7a14d84b..b10ebf74 100644 --- a/examples/baremetal/cmd/baremetal/main.go +++ b/examples/baremetal/cmd/baremetal/main.go @@ -73,6 +73,14 @@ func (s *server) UpdateJobs(_ context.Context, in *emptypb.Empty) (*emptypb.Empt return &emptypb.Empty{}, nil } +// RecoverJob +func (s *server) RecoverJob(_ context.Context, in *pb.Job) (*pb.Job, error) { + slog.Info("RecoverJob") + job, err := s.bm.RecoverJob(baremetal.JobFromPB(in)) + errors.Log(err) + return baremetal.JobToPB(job), err +} + func main() { logx.UserLevel = slog.LevelInfo opts := cli.DefaultOptions("baremetal", "Bare metal server for job running on bare servers over ssh") diff --git a/examples/baremetal/jobs.go b/examples/baremetal/jobs.go index acace12d..b6581606 100644 --- a/examples/baremetal/jobs.go +++ b/examples/baremetal/jobs.go @@ -211,7 +211,9 @@ func (bm *BareMetal) runPendingJobs() (int, error) { return nRun, errors.Join(errs...) } -// cancelJobs cancels list of job IDs. Returns error for jobs not found. +// cancelJobs cancels list of job IDs. +// This is robust to jobs that are not found, and will +// create data for them, as a way of recovering the status of these jobs. func (bm *BareMetal) cancelJobs(jobs ...int) error { var errs []error for _, jid := range jobs { @@ -413,3 +415,12 @@ func (bm *BareMetal) setServerUsedFromJobs() error { } return errors.Join(errs...) } + +// recoverJob attempts to recover the job information +// from given job, using remote or local data. +func (bm *BareMetal) recoverJob(job *Job) (*Job, error) { + // do we need to do anything actually? + bm.Active.Add(job.ID, job) + bm.saveState() + return job, nil +} diff --git a/examples/baremetal/jobs.goal b/examples/baremetal/jobs.goal index 6c7f9014..ded1155c 100644 --- a/examples/baremetal/jobs.goal +++ b/examples/baremetal/jobs.goal @@ -210,7 +210,9 @@ func (bm *BareMetal) runPendingJobs() (int, error) { return nRun, errors.Join(errs...) } -// cancelJobs cancels list of job IDs. Returns error for jobs not found. +// cancelJobs cancels list of job IDs. +// This is robust to jobs that are not found, and will +// create data for them, as a way of recovering the status of these jobs. func (bm *BareMetal) cancelJobs(jobs ...int) error { var errs []error for _, jid := range jobs { @@ -413,3 +415,12 @@ func (bm *BareMetal) setServerUsedFromJobs() error { return errors.Join(errs...) } +// recoverJob attempts to recover the job information +// from given job, using remote or local data. +func (bm *BareMetal) recoverJob(job *Job) (*Job, error) { + // do we need to do anything actually? + bm.Active.Add(job.ID, job) + bm.saveState() + return job, nil +} + diff --git a/examples/simmer/bare.go b/examples/simmer/bare.go index e3b1893f..a9395144 100644 --- a/examples/simmer/bare.go +++ b/examples/simmer/bare.go @@ -13,6 +13,7 @@ import ( "os" "strconv" "strings" + "time" "cogentcore.org/core/base/errors" "cogentcore.org/core/core" @@ -147,3 +148,32 @@ func (sr *Simmer) CancelJobsBare(jobs []string) { } sr.BareMetal.CancelJobs(jnos...) } + +func (sr *Simmer) RecoverJobsBare(jobs []string) { + for _, jid := range jobs { + jno := 0 + sjob := sr.ValueForJob(jid, "ServerJob") + if sjob != "" { + jno = errors.Log1(strconv.Atoi(sjob)) + } else { + fmt.Println("job does not have a ServerJob id") + } + job := &baremetal.Job{ID: jno} + job.Status.SetString(sr.ValueForJob(jid, "Status")) + job.Path = sr.ServerJobPath(jid) + job.Source = sr.Config.Project + job.Script = "job.sbatch" + job.ResultsGlob = sr.Config.FetchFiles + job.Submit = errors.Log1(time.Parse(sr.Config.TimeFormat, sr.ValueForJob(jid, "Submit"))) + job.Start = errors.Log1(time.Parse(sr.Config.TimeFormat, sr.ValueForJob(jid, "Start"))) + job.End = errors.Log1(time.Parse(sr.Config.TimeFormat, sr.ValueForJob(jid, "End"))) + job.ServerName = sr.ValueForJob(jid, "Server") + // todo: PID + // jpath := sr.JobPath(jid) + // @0 + // cd {jpath} + // job.Status = goalib.ReadFile("job.status") + // job.Label = goalib.ReadFile("job.label") + sr.BareMetal.RecoverJob(job) + } +} diff --git a/examples/simmer/bare.goal b/examples/simmer/bare.goal index ed63fc3b..c5abfab1 100644 --- a/examples/simmer/bare.goal +++ b/examples/simmer/bare.goal @@ -10,6 +10,7 @@ import ( "io" "strconv" "strings" + "time" "os" "cogentcore.org/core/base/errors" @@ -146,3 +147,32 @@ func (sr *Simmer) CancelJobsBare(jobs []string) { sr.BareMetal.CancelJobs(jnos...) } +func (sr *Simmer) RecoverJobsBare(jobs []string) { + for _, jid := range jobs { + jno := 0 + sjob := sr.ValueForJob(jid, "ServerJob") + if sjob != "" { + jno = errors.Log1(strconv.Atoi(sjob)) + } else { + fmt.Println("job does not have a ServerJob id") + } + job := &baremetal.Job{ID: jno} + job.Status.SetString(sr.ValueForJob(jid, "Status")) + job.Path = sr.ServerJobPath(jid) + job.Source = sr.Config.Project + job.Script = "job.sbatch" + job.ResultsGlob = sr.Config.FetchFiles + job.Submit = errors.Log1(time.Parse(sr.Config.TimeFormat, sr.ValueForJob(jid, "Submit"))) + job.Start = errors.Log1(time.Parse(sr.Config.TimeFormat, sr.ValueForJob(jid, "Start"))) + job.End = errors.Log1(time.Parse(sr.Config.TimeFormat, sr.ValueForJob(jid, "End"))) + job.ServerName = sr.ValueForJob(jid, "Server") + // todo: PID + // jpath := sr.JobPath(jid) + // @0 + // cd {jpath} + // job.Status = goalib.ReadFile("job.status") + // job.Label = goalib.ReadFile("job.label") + sr.BareMetal.RecoverJob(job) + } +} + diff --git a/examples/simmer/jobs.go b/examples/simmer/jobs.go index e0a2bef9..f6481f3a 100644 --- a/examples/simmer/jobs.go +++ b/examples/simmer/jobs.go @@ -424,3 +424,22 @@ func (sr *Simmer) Archive() { //types:add sr.UpdateSims() }) } + +// Recover recovers the jobs selected in the Jobs table, +// with a confirmation prompt. +func (sr *Simmer) Recover() { //types:add + tv := sr.JobsTableView + jobs := tv.SelectedColumnStrings("JobID") + if len(jobs) == 0 { + core.MessageSnackbar(sr, "No jobs selected for cancel") + return + } + lab.PromptOKCancel(sr, "Ok to recover these jobs: "+strings.Join(jobs, " "), func() { + // if sr.IsSlurm() { + // sr.CancelJobsSlurm(jobs) + // } else { + sr.RecoverJobsBare(jobs) + // } + sr.UpdateSims() + }) +} diff --git a/examples/simmer/jobs.goal b/examples/simmer/jobs.goal index 73e02b1d..25954acc 100644 --- a/examples/simmer/jobs.goal +++ b/examples/simmer/jobs.goal @@ -423,4 +423,23 @@ func (sr *Simmer) Archive() { //types:add }) } +// Recover recovers the jobs selected in the Jobs table, +// with a confirmation prompt. +func (sr *Simmer) Recover() { //types:add + tv := sr.JobsTableView + jobs := tv.SelectedColumnStrings("JobID") + if len(jobs) == 0 { + core.MessageSnackbar(sr, "No jobs selected for cancel") + return + } + lab.PromptOKCancel(sr, "Ok to recover these jobs: " + strings.Join(jobs, " "), func() { + // if sr.IsSlurm() { + // sr.CancelJobsSlurm(jobs) + // } else { + sr.RecoverJobsBare(jobs) + // } + sr.UpdateSims() + }) +} + diff --git a/examples/simmer/simmer.go b/examples/simmer/simmer.go index 17521d16..89f6f67b 100644 --- a/examples/simmer/simmer.go +++ b/examples/simmer/simmer.go @@ -261,6 +261,9 @@ func (sr *Simmer) MakeToolbar(p *tree.Plan) { tree.Add(p, func(w *core.FuncButton) { w.SetFunc(sr.Archive).SetIcon(icons.Archive) }) + tree.Add(p, func(w *core.FuncButton) { + w.SetFunc(sr.Recover).SetIcon(icons.Archive) + }) tree.Add(p, func(w *core.FuncButton) { w.SetFunc(sr.EditConfig).SetIcon(icons.Edit) }) diff --git a/examples/simmer/typegen.go b/examples/simmer/typegen.go index dab15b16..e447236f 100644 --- a/examples/simmer/typegen.go +++ b/examples/simmer/typegen.go @@ -23,7 +23,7 @@ var _ = types.AddType(&types.Type{Name: "main.Configuration", IDName: "configura var _ = types.AddType(&types.Type{Name: "main.Result", IDName: "result", Doc: "Result has info for one loaded result, as a table.Table", Fields: []types.Field{{Name: "JobID", Doc: "job id for results"}, {Name: "Label", Doc: "short label used as a legend in the plot"}, {Name: "Message", Doc: "description of job"}, {Name: "Args", Doc: "args used in running job"}, {Name: "Path", Doc: "path to data"}, {Name: "Table", Doc: "result data"}}}) -var _ = types.AddType(&types.Type{Name: "main.Simmer", IDName: "simmer", Doc: "Simmer manages the running and data analysis of results from simulations\nthat are run on remote server(s), within a Cogent Lab browser environment,\nwith the files as the left panel, and the Tabber as the right panel.", Methods: []types.Method{{Name: "FetchJobBare", Doc: "FetchJobBare downloads results files from bare metal server.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"jid", "force"}}, {Name: "EditConfig", Doc: "EditConfig edits the configuration", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Jobs", Doc: "Jobs updates the Jobs tab with a Table showing all the Jobs\nwith their meta data. Uses the dbmeta.toml data compiled from\nthe Status function.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "UpdateSims", Doc: "Jobs updates the Jobs tab with a Table showing all the Jobs\nwith their meta data. Uses the dbmeta.toml data compiled from\nthe Status function.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Queue", Doc: "Queue runs a queue query command on the server and shows the results.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Status", Doc: "Status gets updated job.* files from the server for any job that\ndoesn't have a Finalized or Fetched status. It updates the\nstatus based on the server job status query, assigning a\nstatus of Finalized if job is done. Updates the dbmeta.toml\ndata based on current job data.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Fetch", Doc: "Fetch retrieves all the .tsv data files from the server\nfor any jobs not already marked as Fetched.\nOperates on the jobs selected in the Jobs table,\nor on all jobs if none selected.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Cancel", Doc: "Cancel cancels the jobs selected in the Jobs table,\nwith a confirmation prompt.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Delete", Doc: "Delete deletes the selected Jobs, with a confirmation prompt.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Archive", Doc: "Archive moves the selected Jobs to the Archive directory,\nlocally, and deletes them from the server,\nfor results that are useful but not immediately relevant.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Results", Doc: "Results loads specific .tsv data files from the jobs selected\nin the Jobs table, into the Results table. There are often\nmultiple result files per job, so this step is necessary to\nchoose which such files to select for plotting.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Diff", Doc: "Diff shows the differences between two selected jobs, or if only\none job is selected, between that job and the current source directory.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Plot", Doc: "Plot concatenates selected Results data files and generates a plot\nof the resulting data.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "PlotMean", Doc: "PlotMean concatenates selected Results data files and generates a plot\nof the resulting data, computing the mean over the values in\n[Config.GroupColumns] to group values (e.g., across Epochs).", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Reset", Doc: "Reset resets the Results table", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Submit", Doc: "Submit submits a job on the server.\nCreates a new job dir based on incrementing counter,\nsynchronizing the job files.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Search", Doc: "Search runs parameter search jobs, one for each parameter.\nThe number of parameters is obtained by running the currently built\nsimulation executable locally with the -search-n argument, which\nreturns the total number of parameter searches to run.\nTHUS, YOU MUST BUILD THE LOCAL SIM WITH THE PARAM SEARCH CONFIGURED.\nThen, it launches that number of jobs with -search-at values from 1..n.\nThe jobs should write a job.label file for the searched parameter.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}}, Embeds: []types.Field{{Name: "Frame"}, {Name: "Browser"}}, Fields: []types.Field{{Name: "Config", Doc: "Config holds all the configuration settings."}, {Name: "JobsTableView", Doc: "JobsTableView is the view of the jobs table."}, {Name: "JobsTable", Doc: "JobsTable is the jobs Table with one row per job."}, {Name: "ResultsTableView", Doc: "ResultsTableView has the results table."}, {Name: "ResultsList", Doc: "ResultsList is the list of result records."}, {Name: "BareMetal", Doc: "BareMetal RPC client."}, {Name: "BareMetalActive", Doc: "Status info from BareMetal"}, {Name: "BareMetalActiveTable"}}}) +var _ = types.AddType(&types.Type{Name: "main.Simmer", IDName: "simmer", Doc: "Simmer manages the running and data analysis of results from simulations\nthat are run on remote server(s), within a Cogent Lab browser environment,\nwith the files as the left panel, and the Tabber as the right panel.", Methods: []types.Method{{Name: "FetchJobBare", Doc: "FetchJobBare downloads results files from bare metal server.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"jid", "force"}}, {Name: "EditConfig", Doc: "EditConfig edits the configuration", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Jobs", Doc: "Jobs updates the Jobs tab with a Table showing all the Jobs\nwith their meta data. Uses the dbmeta.toml data compiled from\nthe Status function.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "UpdateSims", Doc: "Jobs updates the Jobs tab with a Table showing all the Jobs\nwith their meta data. Uses the dbmeta.toml data compiled from\nthe Status function.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Queue", Doc: "Queue runs a queue query command on the server and shows the results.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Status", Doc: "Status gets updated job.* files from the server for any job that\ndoesn't have a Finalized or Fetched status. It updates the\nstatus based on the server job status query, assigning a\nstatus of Finalized if job is done. Updates the dbmeta.toml\ndata based on current job data.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Fetch", Doc: "Fetch retrieves all the .tsv data files from the server\nfor any jobs not already marked as Fetched.\nOperates on the jobs selected in the Jobs table,\nor on all jobs if none selected.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Cancel", Doc: "Cancel cancels the jobs selected in the Jobs table,\nwith a confirmation prompt.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Delete", Doc: "Delete deletes the selected Jobs, with a confirmation prompt.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Archive", Doc: "Archive moves the selected Jobs to the Archive directory,\nlocally, and deletes them from the server,\nfor results that are useful but not immediately relevant.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Recover", Doc: "Recover recovers the jobs selected in the Jobs table,\nwith a confirmation prompt.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Results", Doc: "Results loads specific .tsv data files from the jobs selected\nin the Jobs table, into the Results table. There are often\nmultiple result files per job, so this step is necessary to\nchoose which such files to select for plotting.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Diff", Doc: "Diff shows the differences between two selected jobs, or if only\none job is selected, between that job and the current source directory.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Plot", Doc: "Plot concatenates selected Results data files and generates a plot\nof the resulting data.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "PlotMean", Doc: "PlotMean concatenates selected Results data files and generates a plot\nof the resulting data, computing the mean over the values in\n[Config.GroupColumns] to group values (e.g., across Epochs).", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Reset", Doc: "Reset resets the Results table", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Submit", Doc: "Submit submits a job on the server.\nCreates a new job dir based on incrementing counter,\nsynchronizing the job files.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Search", Doc: "Search runs parameter search jobs, one for each parameter.\nThe number of parameters is obtained by running the currently built\nsimulation executable locally with the -search-n argument, which\nreturns the total number of parameter searches to run.\nTHUS, YOU MUST BUILD THE LOCAL SIM WITH THE PARAM SEARCH CONFIGURED.\nThen, it launches that number of jobs with -search-at values from 1..n.\nThe jobs should write a job.label file for the searched parameter.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}}, Embeds: []types.Field{{Name: "Frame"}, {Name: "Browser"}}, Fields: []types.Field{{Name: "Config", Doc: "Config holds all the configuration settings."}, {Name: "JobsTableView", Doc: "JobsTableView is the view of the jobs table."}, {Name: "JobsTable", Doc: "JobsTable is the jobs Table with one row per job."}, {Name: "ResultsTableView", Doc: "ResultsTableView has the results table."}, {Name: "ResultsList", Doc: "ResultsList is the list of result records."}, {Name: "BareMetal", Doc: "BareMetal RPC client."}, {Name: "BareMetalActive", Doc: "Status info from BareMetal"}, {Name: "BareMetalActiveTable"}}}) // NewSimmer returns a new [Simmer] with the given optional parent: // Simmer manages the running and data analysis of results from simulations diff --git a/go.mod b/go.mod index 7c7b0816..e2f74ffa 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ go 1.23.4 // https://github.com/googleapis/go-genproto/issues/1015 require ( - cogentcore.org/core v0.3.13-0.20250909222513-1cba37a21a69 + cogentcore.org/core v0.3.13-0.20251122080528-7dff9fe2d85b github.com/alecthomas/assert/v2 v2.6.0 github.com/cogentcore/readline v0.1.3 github.com/cogentcore/yaegi v0.0.0-20250622201820-b7838bdd95eb @@ -40,6 +40,7 @@ require ( github.com/dlclark/regexp2 v1.11.0 // indirect github.com/ericchiang/css v1.3.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-fonts/latin-modern v0.3.3 // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect github.com/go-text/typesetting v0.3.1-0.20250402122313-7a0f05577ff5 // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -50,16 +51,17 @@ require ( github.com/hack-pad/hackpadfs v0.2.1 // indirect github.com/hack-pad/safejs v0.1.1 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect github.com/jinzhu/copier v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect - github.com/muesli/termenv v0.15.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/pelletier/go-toml/v2 v2.1.2-0.20240227203013-2b69615b5d55 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/tdewolff/parse/v2 v2.7.19 // indirect github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 // indirect @@ -73,4 +75,7 @@ require ( google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/knuth v0.5.4 // indirect + modernc.org/token v1.1.0 // indirect + star-tex.org/x/tex v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index 00e58493..1dffdaf7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cogentcore.org/core v0.3.13-0.20250909222513-1cba37a21a69 h1:P2aEix7cbJtYrRzBU6xtvQDRWt452OPEi1dcoD6SWUQ= -cogentcore.org/core v0.3.13-0.20250909222513-1cba37a21a69/go.mod h1:8zfSRZhuM/4OTMt8i1Q8W0P2VcD1riw2AgEQNmv8wHQ= +cogentcore.org/core v0.3.13-0.20251122080528-7dff9fe2d85b h1:YF5zD4OoObOKKI2C8atfgJ3giEw1ySTOCqxpUq3OyZs= +cogentcore.org/core v0.3.13-0.20251122080528-7dff9fe2d85b/go.mod h1:eDHnTCy1sBhAKN9NPsSCnBW3VAnwQBNA9nbGMo9r+Xs= github.com/Bios-Marcel/wastebasket/v2 v2.0.3 h1:TkoDPcSqluhLGE+EssHu7UGmLgUEkWg7kNyHyyJ3Q9g= github.com/Bios-Marcel/wastebasket/v2 v2.0.3/go.mod h1:769oPCv6eH7ugl90DYIsWwjZh4hgNmMS3Zuhe1bH6KU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -83,6 +83,8 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -102,8 +104,8 @@ github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lL github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -112,8 +114,9 @@ github.com/pelletier/go-toml/v2 v2.1.2-0.20240227203013-2b69615b5d55/go.mod h1:t github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= diff --git a/gosl/alignsl/alignsl.go b/gosl/alignsl/alignsl.go index 27d39250..e62247a0 100644 --- a/gosl/alignsl/alignsl.go +++ b/gosl/alignsl/alignsl.go @@ -84,6 +84,7 @@ func CheckStruct(cx *Context, st *types.Struct, stName string) bool { kind := bt.Kind() if !(kind == types.Uint32 || kind == types.Int32 || kind == types.Float32 || kind == types.Uint64) { hasErr = cx.AddError(fmt.Sprintf(" %s: basic type != [U]Int32 or Float32: %s", fl.Name(), bt.String()), hasErr, stName) + fmt.Println("kind:", kind, "ft:", ft.String()) } } else { if sst, is := ut.(*types.Struct); is { diff --git a/gosl/examples/basic/compute.go b/gosl/examples/basic/compute.go index dcdeac40..ee1141ce 100644 --- a/gosl/examples/basic/compute.go +++ b/gosl/examples/basic/compute.go @@ -67,7 +67,9 @@ type ParamStruct struct { // 1/Tau Dt float32 - pad float32 + // number of data items -- must have avail for GPU to exclude extra. + DataLen uint32 + pad1 float32 Sub SubStruct @@ -93,6 +95,9 @@ func (ps *ParamStruct) IntegFromRaw(idx int) { // Compute does the main computation. func Compute(i uint32) { //gosl:kernel + if i >= Params[0].DataLen { // note: essential to bounds check b/c i in 64 blocks + return + } Params[0].IntegFromRaw(int(i)) } diff --git a/gosl/examples/basic/compute.goal b/gosl/examples/basic/compute.goal index 6ef71331..4ad27883 100644 --- a/gosl/examples/basic/compute.goal +++ b/gosl/examples/basic/compute.goal @@ -62,7 +62,9 @@ type ParamStruct struct { // 1/Tau Dt float32 - pad float32 + // number of data items -- must have avail for GPU to exclude extra. + DataLen uint32 + pad1 float32 Sub SubStruct @@ -88,6 +90,9 @@ func (ps *ParamStruct) IntegFromRaw(idx int) { // Compute does the main computation. func Compute(i uint32) { //gosl:kernel + if i >= Params[0].DataLen { // note: essential to bounds check b/c i in 64 blocks + return + } Params[0].IntegFromRaw(int(i)) } diff --git a/gosl/examples/basic/gosl.go b/gosl/examples/basic/gosl.go index b22ad45b..6696b5d6 100644 --- a/gosl/examples/basic/gosl.go +++ b/gosl/examples/basic/gosl.go @@ -15,9 +15,19 @@ import ( var shaders embed.FS var ( - // ComputeGPU is the compute gpu device + // GPUInitialized is true once the GPU system has been initialized. + // Prevents multiple initializations. + GPUInitialized bool + + // ComputeGPU is the compute gpu device. + // Set this prior to calling GPUInit() to use an existing device. ComputeGPU *gpu.GPU + // BorrowedGPU is true if our ComputeGPU is set externally, + // versus created specifically for this system. If external, + // we don't release it. + BorrowedGPU bool + // UseGPU indicates whether to use GPU vs. CPU. UseGPU bool ) @@ -41,11 +51,17 @@ var TensorStrides tensor.Uint32 // configuring system(s), variables and kernels. // It is safe to call multiple times: detects if already run. func GPUInit() { - if ComputeGPU != nil { + if GPUInitialized { return } - gp := gpu.NewComputeGPU() - ComputeGPU = gp + GPUInitialized = true + if ComputeGPU == nil { // set prior to this call to use an external + ComputeGPU = gpu.NewComputeGPU() + } else { + BorrowedGPU = true + } + gp := ComputeGPU + _ = fmt.Sprintf("%g",math.NaN()) // keep imports happy { sy := gpu.NewComputeSystem(gp, "Default") @@ -103,10 +119,11 @@ func GPURelease() { GPUSystem = nil } - if ComputeGPU != nil { + if !BorrowedGPU && ComputeGPU != nil { ComputeGPU.Release() - ComputeGPU = nil + } + ComputeGPU = nil } // RunAtomic runs the Atomic kernel with given number of elements, @@ -222,7 +239,7 @@ func ToGPU(vars ...GPUVars) { v, _ := syVars.ValueByIndex(0, "Params", 0) gpu.SetValueFrom(v, Params) case DataVar: - bsz := 536870912 + bsz := 536870904 n := Data.Len() nb := int(math.Ceil(float64(n) / float64(bsz))) for bi := range nb { @@ -274,7 +291,7 @@ func ReadFromGPU(vars ...GPUVars) { v, _ := syVars.ValueByIndex(0, "Params", 0) v.GPUToRead(sy.CommandEncoder) case DataVar: - bsz := 536870912 + bsz := 536870904 n := Data.Len() nb := int(math.Ceil(float64(n) / float64(bsz))) for bi := range nb { @@ -299,7 +316,7 @@ func SyncFromGPU(vars ...GPUVars) { v.ReadSync() gpu.ReadToBytes(v, Params) case DataVar: - bsz := 536870912 + bsz := 536870904 n := Data.Len() nb := int(math.Ceil(float64(n) / float64(bsz))) for bi := range nb { diff --git a/gosl/examples/basic/main.go b/gosl/examples/basic/main.go index 2952cacb..e71e9080 100644 --- a/gosl/examples/basic/main.go +++ b/gosl/examples/basic/main.go @@ -39,6 +39,7 @@ func main() { Data = tensor.NewFloat32() Data.SetShapeSizes(n, 3) nt := Data.Len() + Params[0].DataLen = uint32(nt) IntData = tensor.NewInt32() IntData.SetShapeSizes(n, 3) diff --git a/gosl/examples/basic/shaders/Atomic.wgsl b/gosl/examples/basic/shaders/Atomic.wgsl index 4a78c6b4..81700ea0 100644 --- a/gosl/examples/basic/shaders/Atomic.wgsl +++ b/gosl/examples/basic/shaders/Atomic.wgsl @@ -35,7 +35,7 @@ struct SubStruct { struct ParamStruct { Tau: f32, Dt: f32, - pad: f32, + DataLen: u32, pad1: f32, Sub: SubStruct, } diff --git a/gosl/examples/basic/shaders/Compute.wgsl b/gosl/examples/basic/shaders/Compute.wgsl index a64c2e67..c136db09 100644 --- a/gosl/examples/basic/shaders/Compute.wgsl +++ b/gosl/examples/basic/shaders/Compute.wgsl @@ -33,181 +33,181 @@ fn main(@builtin(workgroup_id) wgid: vec3, @builtin(num_workgroups) nwg: ve } fn DataGet(ix: u32) -> f32 { - let ii = ix / 536870912; + let ii = ix / 536870904; switch ii { case u32(0): { return Data0[ix]; } case u32(1): { - return Data1[ix - 536870912]; + return Data1[ix - 536870904]; } case u32(2): { - return Data2[ix - 1073741824]; + return Data2[ix - 1073741808]; } case u32(3): { - return Data3[ix - 1610612736]; + return Data3[ix - 1610612712]; } case u32(4): { - return Data4[ix - 2147483648]; + return Data4[ix - 2147483616]; } case u32(5): { - return Data5[ix - 2684354560]; + return Data5[ix - 2684354520]; } case u32(6): { - return Data6[ix - 3221225472]; + return Data6[ix - 3221225424]; } default: { - return Data7[ix - 3758096384]; + return Data7[ix - 3758096328]; } } } fn DataSet(vl: f32, ix: u32) { - let ii = ix / 536870912; + let ii = ix / 536870904; switch ii { case u32(0): { Data0[ix] = vl; } case u32(1): { - Data1[ix - 536870912] = vl; + Data1[ix - 536870904] = vl; } case u32(2): { - Data2[ix - 1073741824] = vl; + Data2[ix - 1073741808] = vl; } case u32(3): { - Data3[ix - 1610612736] = vl; + Data3[ix - 1610612712] = vl; } case u32(4): { - Data4[ix - 2147483648] = vl; + Data4[ix - 2147483616] = vl; } case u32(5): { - Data5[ix - 2684354560] = vl; + Data5[ix - 2684354520] = vl; } case u32(6): { - Data6[ix - 3221225472] = vl; + Data6[ix - 3221225424] = vl; } default: { - Data7[ix - 3758096384] = vl; + Data7[ix - 3758096328] = vl; } } } fn DataSetAdd(vl: f32, ix: u32) { - let ii = ix / 536870912; + let ii = ix / 536870904; switch ii { case u32(0): { Data0[ix] += vl; } case u32(1): { - Data1[ix - 536870912] += vl; + Data1[ix - 536870904] += vl; } case u32(2): { - Data2[ix - 1073741824] += vl; + Data2[ix - 1073741808] += vl; } case u32(3): { - Data3[ix - 1610612736] += vl; + Data3[ix - 1610612712] += vl; } case u32(4): { - Data4[ix - 2147483648] += vl; + Data4[ix - 2147483616] += vl; } case u32(5): { - Data5[ix - 2684354560] += vl; + Data5[ix - 2684354520] += vl; } case u32(6): { - Data6[ix - 3221225472] += vl; + Data6[ix - 3221225424] += vl; } default: { - Data7[ix - 3758096384] += vl; + Data7[ix - 3758096328] += vl; } } } fn DataSetSub(vl: f32, ix: u32) { - let ii = ix / 536870912; + let ii = ix / 536870904; switch ii { case u32(0): { Data0[ix] -= vl; } case u32(1): { - Data1[ix - 536870912] -= vl; + Data1[ix - 536870904] -= vl; } case u32(2): { - Data2[ix - 1073741824] -= vl; + Data2[ix - 1073741808] -= vl; } case u32(3): { - Data3[ix - 1610612736] -= vl; + Data3[ix - 1610612712] -= vl; } case u32(4): { - Data4[ix - 2147483648] -= vl; + Data4[ix - 2147483616] -= vl; } case u32(5): { - Data5[ix - 2684354560] -= vl; + Data5[ix - 2684354520] -= vl; } case u32(6): { - Data6[ix - 3221225472] -= vl; + Data6[ix - 3221225424] -= vl; } default: { - Data7[ix - 3758096384] -= vl; + Data7[ix - 3758096328] -= vl; } } } fn DataSetMul(vl: f32, ix: u32) { - let ii = ix / 536870912; + let ii = ix / 536870904; switch ii { case u32(0): { Data0[ix] *= vl; } case u32(1): { - Data1[ix - 536870912] *= vl; + Data1[ix - 536870904] *= vl; } case u32(2): { - Data2[ix - 1073741824] *= vl; + Data2[ix - 1073741808] *= vl; } case u32(3): { - Data3[ix - 1610612736] *= vl; + Data3[ix - 1610612712] *= vl; } case u32(4): { - Data4[ix - 2147483648] *= vl; + Data4[ix - 2147483616] *= vl; } case u32(5): { - Data5[ix - 2684354560] *= vl; + Data5[ix - 2684354520] *= vl; } case u32(6): { - Data6[ix - 3221225472] *= vl; + Data6[ix - 3221225424] *= vl; } default: { - Data7[ix - 3758096384] *= vl; + Data7[ix - 3758096328] *= vl; } } } fn DataSetDiv(vl: f32, ix: u32) { - let ii = ix / 536870912; + let ii = ix / 536870904; switch ii { case u32(0): { Data0[ix] /= vl; } case u32(1): { - Data1[ix - 536870912] /= vl; + Data1[ix - 536870904] /= vl; } case u32(2): { - Data2[ix - 1073741824] /= vl; + Data2[ix - 1073741808] /= vl; } case u32(3): { - Data3[ix - 1610612736] /= vl; + Data3[ix - 1610612712] /= vl; } case u32(4): { - Data4[ix - 2147483648] /= vl; + Data4[ix - 2147483616] /= vl; } case u32(5): { - Data5[ix - 2684354560] /= vl; + Data5[ix - 2684354520] /= vl; } case u32(6): { - Data6[ix - 3221225472] /= vl; + Data6[ix - 3221225424] /= vl; } default: { - Data7[ix - 3758096384] /= vl; + Data7[ix - 3758096328] /= vl; } } } @@ -231,7 +231,7 @@ struct SubStruct { struct ParamStruct { Tau: f32, Dt: f32, - pad: f32, + DataLen: u32, pad1: f32, Sub: SubStruct, } @@ -250,6 +250,9 @@ fn ParamStruct_IntegFromRaw(ps: ParamStruct, idx: i32) { SubStruct_IntegFromRaw(ps.Sub, idx); } fn Compute(i: u32) { //gosl:kernel + if (i >= Params[0].DataLen) { // note: essential to bounds check b/c i in 64 blocks + return; + } let params=Params[0]; ParamStruct_IntegFromRaw(params, i32(i)); } diff --git a/gosl/examples/rand/gosl.go b/gosl/examples/rand/gosl.go index c5e1ef80..66a8fd00 100644 --- a/gosl/examples/rand/gosl.go +++ b/gosl/examples/rand/gosl.go @@ -15,9 +15,19 @@ import ( var shaders embed.FS var ( - // ComputeGPU is the compute gpu device + // GPUInitialized is true once the GPU system has been initialized. + // Prevents multiple initializations. + GPUInitialized bool + + // ComputeGPU is the compute gpu device. + // Set this prior to calling GPUInit() to use an existing device. ComputeGPU *gpu.GPU + // BorrowedGPU is true if our ComputeGPU is set externally, + // versus created specifically for this system. If external, + // we don't release it. + BorrowedGPU bool + // UseGPU indicates whether to use GPU vs. CPU. UseGPU bool ) @@ -41,11 +51,17 @@ var TensorStrides tensor.Uint32 // configuring system(s), variables and kernels. // It is safe to call multiple times: detects if already run. func GPUInit() { - if ComputeGPU != nil { + if GPUInitialized { return } - gp := gpu.NewComputeGPU() - ComputeGPU = gp + GPUInitialized = true + if ComputeGPU == nil { // set prior to this call to use an external + ComputeGPU = gpu.NewComputeGPU() + } else { + BorrowedGPU = true + } + gp := ComputeGPU + _ = fmt.Sprintf("%g",math.NaN()) // keep imports happy { sy := gpu.NewComputeSystem(gp, "Default") @@ -81,10 +97,11 @@ func GPURelease() { GPUSystem = nil } - if ComputeGPU != nil { + if !BorrowedGPU && ComputeGPU != nil { ComputeGPU.Release() - ComputeGPU = nil + } + ComputeGPU = nil } // RunCompute runs the Compute kernel with given number of elements, diff --git a/gosl/examples/rand/rand.go b/gosl/examples/rand/rand.go index 198a9b4a..09d8f96c 100644 --- a/gosl/examples/rand/rand.go +++ b/gosl/examples/rand/rand.go @@ -67,6 +67,7 @@ func RndGen(counter uint64, idx uint32) { } func Compute(i uint32) { //gosl:kernel + // note: this should have a bounds check here on i -- can be larger than Floats RndGen(Seed[0].Seed, i) } diff --git a/gosl/examples/rand/rand.goal b/gosl/examples/rand/rand.goal index 308620e6..ae25ab8b 100644 --- a/gosl/examples/rand/rand.goal +++ b/gosl/examples/rand/rand.goal @@ -62,6 +62,7 @@ func RndGen(counter uint64, idx uint32) { } func Compute(i uint32) { //gosl:kernel + // note: this should have a bounds check here on i -- can be larger than Floats RndGen(Seed[0].Seed, i) } diff --git a/gosl/gotosl/extract.go b/gosl/gotosl/extract.go index 4c6d3fe9..ed6cad08 100644 --- a/gosl/gotosl/extract.go +++ b/gosl/gotosl/extract.go @@ -153,6 +153,7 @@ func (st *State) AppendGoHeader(lines [][]byte) [][]byte { "cogentcore.org/lab/gosl/slbool" "cogentcore.org/lab/gosl/slrand" "cogentcore.org/lab/gosl/sltype" + "cogentcore.org/lab/gosl/slvec" "cogentcore.org/lab/tensor" `)) for impath := range st.GoImports { diff --git a/gosl/gotosl/gengpu.go b/gosl/gotosl/gengpu.go index bc8e1d94..4e6afa55 100644 --- a/gosl/gotosl/gengpu.go +++ b/gosl/gotosl/gengpu.go @@ -49,9 +49,19 @@ import ( var shaders embed.FS var ( - // ComputeGPU is the compute gpu device + // GPUInitialized is true once the GPU system has been initialized. + // Prevents multiple initializations. + GPUInitialized bool + + // ComputeGPU is the compute gpu device. + // Set this prior to calling GPUInit() to use an existing device. ComputeGPU *gpu.GPU + // BorrowedGPU is true if our ComputeGPU is set externally, + // versus created specifically for this system. If external, + // we don't release it. + BorrowedGPU bool + // UseGPU indicates whether to use GPU vs. CPU. UseGPU bool ) @@ -111,11 +121,17 @@ const ( // configuring system(s), variables and kernels. // It is safe to call multiple times: detects if already run. func GPUInit() { - if ComputeGPU != nil { + if GPUInitialized { return } - gp := gpu.NewComputeGPU() - ComputeGPU = gp + GPUInitialized = true + if ComputeGPU == nil { // set prior to this call to use an external + ComputeGPU = gpu.NewComputeGPU() + } else { + BorrowedGPU = true + } + gp := ComputeGPU + _ = fmt.Sprintf("%g",math.NaN()) // keep imports happy ` @@ -146,10 +162,11 @@ func GPURelease() { } gpuRelease := ` - if ComputeGPU != nil { + if !BorrowedGPU && ComputeGPU != nil { ComputeGPU.Release() - ComputeGPU = nil + } + ComputeGPU = nil } ` diff --git a/gosl/gotosl/nodes.go b/gosl/gotosl/nodes.go index 99b1525e..0b3afc73 100644 --- a/gosl/gotosl/nodes.go +++ b/gosl/gotosl/nodes.go @@ -2766,6 +2766,13 @@ func (p *printer) systemVars(d *ast.GenDecl, sysname string) { p.userError(err) continue } + // by the time this happens, all types have been moved to imports and show up there + // so we've lost the original origin. And we'd have to make up an incompatible type name + // anyway, so bottom line is: all var types need to be defined locally. + // tt := p.getIdType(id) + // if tt != nil { + // fmt.Println("idtyp:", tt.String()) + // } typ = "[]" + id.Name } else { sel, ok := vs.Type.(*ast.SelectorExpr) diff --git a/gosl/gotosl/sledits.go b/gosl/gotosl/sledits.go index 41718c49..c783c3a2 100644 --- a/gosl/gotosl/sledits.go +++ b/gosl/gotosl/sledits.go @@ -39,6 +39,12 @@ type Replace struct { var Replaces = []Replace{ {[]byte("sltype.Uint32Vec2"), []byte("vec2")}, {[]byte("sltype.Float32Vec2"), []byte("vec2")}, + {[]byte("slvec.Vector2i"), []byte("vec4")}, + {[]byte("slvec.Vector2"), []byte("vec4")}, + {[]byte("math32.Vector2i"), []byte("vec2")}, + {[]byte("math32.Vector2"), []byte("vec2")}, + {[]byte("math32.Vector3"), []byte("vec3")}, + {[]byte("math32.Vector4"), []byte("vec4")}, {[]byte("float32"), []byte("f32")}, {[]byte("float64"), []byte("f64")}, // TODO: not yet supported {[]byte("uint32"), []byte("u32")}, diff --git a/gosl/gotosl/testdata/Compute.golden b/gosl/gotosl/testdata/Compute.golden index 75abc4e1..abc0f07d 100644 --- a/gosl/gotosl/testdata/Compute.golden +++ b/gosl/gotosl/testdata/Compute.golden @@ -154,6 +154,8 @@ struct ParamStruct { Dt: f32, Option: i32, // note: standard bool doesn't work pad: f32, // comment this out to trigger alignment warning + VXYf: vec4, // translates to vec4 + VXYi: vec4, // translates to vec4 Subs: SubParamStruct, } fn ParamStruct_IntegFromRaw(ps: ParamStruct, idx: i32) -> f32 { @@ -165,7 +167,7 @@ fn ParamStruct_IntegFromRaw(ps: ParamStruct, idx: i32) -> f32 { integ += newVal; Data[Index2D(TensorStrides[0], TensorStrides[1], u32(idx), u32(Integ))] = integ; Data[Index2D(TensorStrides[0], TensorStrides[1], u32(idx), u32(Exp))] = exp(-integ); - var a: f32; + var a = ps.VXYf.x; let ctx = Ctx[0]; ParamStruct_AnotherMeth(ps, ctx, idx, &a); var bv = BigGet(Index2D(TensorStrides[10], TensorStrides[11], u32(idx), u32(Integ))); diff --git a/gosl/gotosl/testdata/CycleUpdt.golden b/gosl/gotosl/testdata/CycleUpdt.golden index 2f79bc09..de6861f7 100644 --- a/gosl/gotosl/testdata/CycleUpdt.golden +++ b/gosl/gotosl/testdata/CycleUpdt.golden @@ -44,6 +44,8 @@ struct ParamStruct { Dt: f32, Option: i32, // note: standard bool doesn't work pad: f32, // comment this out to trigger alignment warning + VXYf: vec4, // translates to vec4 + VXYi: vec4, // translates to vec4 Subs: SubParamStruct, } struct Context { diff --git a/gosl/gotosl/testdata/basic.go b/gosl/gotosl/testdata/basic.go index 9d65206d..6ff432f4 100644 --- a/gosl/gotosl/testdata/basic.go +++ b/gosl/gotosl/testdata/basic.go @@ -8,6 +8,7 @@ import ( "cogentcore.org/core/math32" "cogentcore.org/lab/gosl/slbool" + "cogentcore.org/lab/gosl/slvec" "cogentcore.org/lab/tensor" ) @@ -126,6 +127,9 @@ type ParamStruct struct { pad float32 // comment this out to trigger alignment warning + VXYf slvec.Vector2 // translates to vec4 + VXYi slvec.Vector2i // translates to vec4 + // extra parameters Subs SubParamStruct } @@ -140,7 +144,9 @@ func (ps *ParamStruct) IntegFromRaw(idx int) float32 { integ += newVal Data.Set(integ, int(idx), int(Integ)) Data.Set(math32.Exp(-integ), int(idx), int(Exp)) - var a float32 + + a := ps.VXYf.X + ctx := GetCtx(0) ps.AnotherMeth(ctx, idx, &a) bv := Big.Value(int(idx), int(Integ)) diff --git a/gosl/gotosl/testdata/basic.goal b/gosl/gotosl/testdata/basic.goal index 54ea3d69..b9890bed 100644 --- a/gosl/gotosl/testdata/basic.goal +++ b/gosl/gotosl/testdata/basic.goal @@ -5,6 +5,7 @@ import ( "cogentcore.org/core/math32" "cogentcore.org/lab/gosl/slbool" + "cogentcore.org/lab/gosl/slvec" "cogentcore.org/lab/tensor" ) @@ -119,6 +120,9 @@ type ParamStruct struct { pad float32 // comment this out to trigger alignment warning + VXYf slvec.Vector2 // translates to vec4 + VXYi slvec.Vector2i // translates to vec4 + // extra parameters Subs SubParamStruct } @@ -133,7 +137,9 @@ func (ps *ParamStruct) IntegFromRaw(idx int) float32 { integ += newVal Data[idx, Integ] = integ Data[idx, Exp] = math32.Exp(-integ) - var a float32 + + a := ps.VXYf.X + ctx := GetCtx(0) ps.AnotherMeth(ctx, idx, &a) bv := Big[idx, Integ] diff --git a/gosl/gotosl/testdata/gosl.golden b/gosl/gotosl/testdata/gosl.golden index 7d49af58..10f1b1b9 100644 --- a/gosl/gotosl/testdata/gosl.golden +++ b/gosl/gotosl/testdata/gosl.golden @@ -15,9 +15,19 @@ import ( var shaders embed.FS var ( - // ComputeGPU is the compute gpu device + // GPUInitialized is true once the GPU system has been initialized. + // Prevents multiple initializations. + GPUInitialized bool + + // ComputeGPU is the compute gpu device. + // Set this prior to calling GPUInit() to use an existing device. ComputeGPU *gpu.GPU + // BorrowedGPU is true if our ComputeGPU is set externally, + // versus created specifically for this system. If external, + // we don't release it. + BorrowedGPU bool + // UseGPU indicates whether to use GPU vs. CPU. UseGPU bool ) @@ -42,11 +52,17 @@ var TensorStrides tensor.Uint32 // configuring system(s), variables and kernels. // It is safe to call multiple times: detects if already run. func GPUInit() { - if ComputeGPU != nil { + if GPUInitialized { return } - gp := gpu.NewComputeGPU() - ComputeGPU = gp + GPUInitialized = true + if ComputeGPU == nil { // set prior to this call to use an external + ComputeGPU = gpu.NewComputeGPU() + } else { + BorrowedGPU = true + } + gp := ComputeGPU + _ = fmt.Sprintf("%g",math.NaN()) // keep imports happy { sy := gpu.NewComputeSystem(gp, "Default") @@ -97,10 +113,11 @@ func GPURelease() { GPUSystem = nil } - if ComputeGPU != nil { + if !BorrowedGPU && ComputeGPU != nil { ComputeGPU.Release() - ComputeGPU = nil + } + ComputeGPU = nil } // RunCompute runs the Compute kernel with given number of elements, diff --git a/gosl/sltype/int.go b/gosl/sltype/int.go index 70468d4c..9caaa314 100644 --- a/gosl/sltype/int.go +++ b/gosl/sltype/int.go @@ -20,6 +20,26 @@ type Int32Vec4 struct { W int32 } +// Add returns the vector p+q. +func (p Int32Vec4) Add(q Int32Vec4) Int32Vec4 { + return Int32Vec4{p.X + q.X, p.Y + q.Y, p.Z + q.Z, p.W + q.W} +} + +// Sub returns the vector p-q. +func (p Int32Vec4) Sub(q Int32Vec4) Int32Vec4 { + return Int32Vec4{p.X - q.X, p.Y - q.Y, p.Z - q.Z, p.W - q.W} +} + +// MulScalar returns the vector p*k. +func (p Int32Vec4) MulScalar(k int32) Int32Vec4 { + return Int32Vec4{p.X * k, p.Y * k, p.Z * k, p.W * k} +} + +// DivScalar returns the vector p/k. +func (p Int32Vec4) DivScalar(k int32) Int32Vec4 { + return Int32Vec4{p.X / k, p.Y / k, p.Z / k, p.W / k} +} + //////// Unsigned // Uint32Vec2 is a length 2 vector of uint32 diff --git a/gosl/slvec/slvec.go b/gosl/slvec/slvec.go new file mode 100644 index 00000000..1171d107 --- /dev/null +++ b/gosl/slvec/slvec.go @@ -0,0 +1,63 @@ +// Copyright 2025 Cogent Core. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slvec + +import "cogentcore.org/core/math32" + +//gosl:start + +// Vector2 is a 2D vector/point with X and Y components. +// with padding values so it works in a GPU struct. Use the +// V() method to get a math32.Vector2 that supports standard +// math operations. Cannot use those math ops in gosl GPU +// code at this point, unfortunately. +type Vector2 struct { + X float32 + Y float32 + + pad, pad1 float32 +} + +func (v *Vector2) V() math32.Vector2 { + return math32.Vec2(v.X, v.Y) +} + +func (v *Vector2) Set(x, y float32) { + v.X = x + v.Y = y +} + +func (v *Vector2) SetV(mv math32.Vector2) { + v.X = mv.X + v.Y = mv.Y +} + +// Vector2i is a 2D vector/point with X and Y integer components. +// with padding values so it works in a GPU struct. Use the +// V() method to get a math32.Vector2i that supports standard +// math operations. Cannot use those math ops in gosl GPU +// code at this point, unfortunately. +type Vector2i struct { + X int32 + Y int32 + + pad, pad1 int32 +} + +func (v *Vector2i) V() math32.Vector2i { + return math32.Vec2i(int(v.X), int(v.Y)) +} + +func (v *Vector2i) Set(x, y int) { + v.X = int32(x) + v.Y = int32(y) +} + +func (v *Vector2i) SetV(mv math32.Vector2i) { + v.X = mv.X + v.Y = mv.Y +} + +//gosl:end diff --git a/plot/plots/xy.go b/plot/plots/xy.go index 4fb3ed5d..83e52fde 100644 --- a/plot/plots/xy.go +++ b/plot/plots/xy.go @@ -270,7 +270,7 @@ func (ln *XY) Plot(plt *plot.Plot) { } } if ln.Size != nil { - ln.Style.Point.Size.Dots *= float32(plt.SizeAxis.Norm(ln.Size.Float1D(i))) + ln.Style.Point.Size.Dots = 1 + ln.Style.Point.Size.Dots*float32(plt.SizeAxis.Norm(ln.Size.Float1D(i))) } ln.Style.Point.SetColorIndex(pc, i) ln.Style.Point.DrawShape(pc, math32.Vec2(ptx, pty)) diff --git a/plot/text.go b/plot/text.go index 54e1ca88..c5188b43 100644 --- a/plot/text.go +++ b/plot/text.go @@ -104,7 +104,7 @@ func (tx *Text) Config(pt *Plot) { // txs := &pt.StandardTextStyle rt, _ := htmltext.HTMLToRich([]byte(tx.Text), fs, nil) - tx.PaintText = pt.TextShaper.WrapLines(rt, fs, ts, &rich.DefaultSettings, math32.Vec2(hsz, fht)) + tx.PaintText = pt.TextShaper.WrapLines(rt, fs, ts, math32.Vec2(hsz, fht)) } func (tx *Text) ToDots(uc *units.Context) { diff --git a/plotcore/editor.go b/plotcore/editor.go index 6e3d82ea..01e0485c 100644 --- a/plotcore/editor.go +++ b/plotcore/editor.go @@ -169,7 +169,7 @@ func (pl *Editor) SetSlice(sl any, stylers ...func(s *plot.Style)) *Editor { return pl.SetTable(dt) } -// SaveSVG saves the plot to an svg -- first updates to ensure that plot is current +// SaveSVG saves the plot to an SVG file. func (pl *Editor) SaveSVG(fname core.Filename) { //types:add plt := pl.plotWidget.Plot mp := plt.PaintBox.Min @@ -185,6 +185,21 @@ func (pl *Editor) SaveSVG(fname core.Filename) { //types:add pl.svgFile = fname } +// SavePDF saves the plot to a PDF file. +func (pl *Editor) SavePDF(fname core.Filename) { //types:add + plt := pl.plotWidget.Plot + mp := plt.PaintBox.Min + plt.PaintBox = plt.PaintBox.Sub(mp) + ptr := paint.NewPainter(math32.FromPoint(plt.PaintBox.Size())) + ptr.Paint.UnitContext = pl.Styles.UnitContext // preserve DPI from current + pd := paint.RenderToPDF(plt.Draw(ptr)) + err := os.WriteFile(string(fname), pd, 0666) + plt.PaintBox = plt.PaintBox.Add(mp) + if err != nil { + core.ErrorSnackbar(pl, err) + } +} + // SaveImage saves the current plot as an image (e.g., png). func (pl *Editor) SaveImage(fname core.Filename) { //types:add plt := pl.plotWidget.Plot @@ -211,6 +226,7 @@ func (pl *Editor) SaveAll(fname core.Filename) { //types:add pl.SaveCSV(core.Filename(fn+".tsv"), tensor.Tab) pl.SaveImage(core.Filename(fn + ".png")) pl.SaveSVG(core.Filename(fn + ".svg")) + pl.SavePDF(core.Filename(fn + ".pdf")) } // OpenCSV opens the Table data from a csv (comma-separated values) file (or any delim) @@ -624,6 +640,7 @@ func (pl *Editor) MakeToolbar(p *tree.Plan) { tree.Add(p, func(w *core.Button) { w.SetText("Save").SetIcon(icons.Save).SetMenu(func(m *core.Scene) { core.NewFuncButton(m).SetFunc(pl.SaveSVG).SetIcon(icons.Save) + core.NewFuncButton(m).SetFunc(pl.SavePDF).SetIcon(icons.Save) core.NewFuncButton(m).SetFunc(pl.SaveImage).SetIcon(icons.Save) core.NewFuncButton(m).SetFunc(pl.SaveCSV).SetIcon(icons.Save) core.NewSeparator(m) diff --git a/plotcore/typegen.go b/plotcore/typegen.go index a87fbd59..27e4bc53 100644 --- a/plotcore/typegen.go +++ b/plotcore/typegen.go @@ -8,7 +8,7 @@ import ( "cogentcore.org/lab/plot" ) -var _ = types.AddType(&types.Type{Name: "cogentcore.org/lab/plotcore.Editor", IDName: "editor", Doc: "Editor is a widget that provides an interactive 2D plot\nof selected columns of tabular data, represented by a [table.Table] into\na [table.Table]. Other types of tabular data can be converted into this format.\nThe user can change various options for the plot and also modify the underlying data.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Methods: []types.Method{{Name: "SaveSVG", Doc: "SaveSVG saves the plot to an svg -- first updates to ensure that plot is current", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}}, {Name: "SaveImage", Doc: "SaveImage saves the current plot as an image (e.g., png).", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}}, {Name: "SaveCSV", Doc: "SaveCSV saves the Table data to a csv (comma-separated values) file with headers (any delim)", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname", "delim"}}, {Name: "SaveAll", Doc: "SaveAll saves the current plot to a png, svg, and the data to a tsv -- full save\nAny extension is removed and appropriate extensions are added", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}}, {Name: "OpenCSV", Doc: "OpenCSV opens the Table data from a csv (comma-separated values) file (or any delim)", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"filename", "delim"}}, {Name: "setColumnsByName", Doc: "setColumnsByName turns columns on or off if their name contains\nthe given string.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"nameContains", "on"}}}, Embeds: []types.Field{{Name: "Frame"}}, Fields: []types.Field{{Name: "table", Doc: "table is the table of data being plotted."}, {Name: "PlotStyle", Doc: "PlotStyle has the overall plot style parameters."}, {Name: "plot", Doc: "plot is the plot object."}, {Name: "svgFile", Doc: "current svg file"}, {Name: "dataFile", Doc: "current csv data file"}, {Name: "inPlot", Doc: "currently doing a plot"}, {Name: "columnsFrame"}, {Name: "plotWidget"}, {Name: "plotStyleModified"}}}) +var _ = types.AddType(&types.Type{Name: "cogentcore.org/lab/plotcore.Editor", IDName: "editor", Doc: "Editor is a widget that provides an interactive 2D plot\nof selected columns of tabular data, represented by a [table.Table] into\na [table.Table]. Other types of tabular data can be converted into this format.\nThe user can change various options for the plot and also modify the underlying data.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Methods: []types.Method{{Name: "SaveSVG", Doc: "SaveSVG saves the plot to an SVG file.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}}, {Name: "SavePDF", Doc: "SavePDF saves the plot to a PDF file.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}}, {Name: "SaveImage", Doc: "SaveImage saves the current plot as an image (e.g., png).", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}}, {Name: "SaveCSV", Doc: "SaveCSV saves the Table data to a csv (comma-separated values) file with headers (any delim)", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname", "delim"}}, {Name: "SaveAll", Doc: "SaveAll saves the current plot to a png, svg, and the data to a tsv -- full save\nAny extension is removed and appropriate extensions are added", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}}, {Name: "OpenCSV", Doc: "OpenCSV opens the Table data from a csv (comma-separated values) file (or any delim)", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"filename", "delim"}}, {Name: "setColumnsByName", Doc: "setColumnsByName turns columns on or off if their name contains\nthe given string.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"nameContains", "on"}}}, Embeds: []types.Field{{Name: "Frame"}}, Fields: []types.Field{{Name: "table", Doc: "table is the table of data being plotted."}, {Name: "PlotStyle", Doc: "PlotStyle has the overall plot style parameters."}, {Name: "plot", Doc: "plot is the plot object."}, {Name: "svgFile", Doc: "current svg file"}, {Name: "dataFile", Doc: "current csv data file"}, {Name: "inPlot", Doc: "currently doing a plot"}, {Name: "columnsFrame"}, {Name: "plotWidget"}, {Name: "plotStyleModified"}}}) // NewEditor returns a new [Editor] with the given optional parent: // Editor is a widget that provides an interactive 2D plot diff --git a/tensor/convert.go b/tensor/convert.go index 1db709ee..72b5801b 100644 --- a/tensor/convert.go +++ b/tensor/convert.go @@ -234,7 +234,7 @@ func Range(vals Values) (min, max float64, minIndex, maxIndex int) { maxIndex = -1 n := vals.Len() for j := range n { - fv := vals.Float1D(n) + fv := vals.Float1D(j) if math.IsNaN(fv) { continue } @@ -307,3 +307,24 @@ func ContainsString(tsr, vals Tensor, opts StringMatch) bool { } return false } + +// CopyFromLargerShape copies values from another tensor of a larger +// shape, using indexes in this shape. The other tensor must have at +// least the same or greater shape values on each dimension as the target. +// Uses float numbers to copy if not a string. +func CopyFromLargerShape(tsr, from Tensor) { + n := tsr.Len() + if tsr.IsString() { + for i := range n { + idx := tsr.Shape().IndexFrom1D(i) + v := from.StringValue(idx...) + tsr.SetString(v, idx...) + } + } else { + for i := range n { + idx := tsr.Shape().IndexFrom1D(i) + v := from.Float(idx...) + tsr.SetFloat(v, idx...) + } + } +} diff --git a/tensorcore/tensorgrid.go b/tensorcore/tensorgrid.go index 47d8c8a0..184fa652 100644 --- a/tensorcore/tensorgrid.go +++ b/tensorcore/tensorgrid.go @@ -197,7 +197,7 @@ func (tg *TensorGrid) SizeLabel(lbs []string, col bool) (minBlank, ngps int, sz if ts != nil { sty, tsty := tg.Styles.NewRichText() tx := rich.NewText(sty, []rune(lbs[mxi])) - lns := ts.WrapLines(tx, sty, tsty, &rich.DefaultSettings, math32.Vec2(10000, 1000)) + lns := ts.WrapLines(tx, sty, tsty, math32.Vec2(10000, 1000)) sz = lns.Bounds.Size().Ceil() if col { sz.X, sz.Y = sz.Y, sz.X @@ -314,7 +314,7 @@ func (tg *TensorGrid) Render() { } yex := float32(ygp) * dimEx tx := rich.NewText(sty, []rune(lb)) - lns := ts.WrapLines(tx, sty, tsty, &rich.DefaultSettings, math32.Vec2(10000, 1000)) + lns := ts.WrapLines(tx, sty, tsty, math32.Vec2(10000, 1000)) cr := math32.Vec2(0, float32(y)+yex) pr := epos.Add(cr.Mul(gsz)) pc.DrawText(lns, pr) @@ -343,7 +343,7 @@ func (tg *TensorGrid) Render() { } xex := float32(xgp) * dimEx tx := rich.NewText(sty, []rune(lb)) - lns := ts.WrapLines(tx, sty, tsty, &rich.DefaultSettings, math32.Vec2(10000, 1000)) + lns := ts.WrapLines(tx, sty, tsty, math32.Vec2(10000, 1000)) cr := math32.Vec2(float32(x)+xex, 0) pr := epos.Add(cr.Mul(gsz)) rot := tg.GridStyle.ColumnRotation